1030. 距离顺序排列矩阵单元格

1030. 距离顺序排列矩阵单元格

给出 R 行 C 列的矩阵,其中的单元格的整数坐标为 (r, c),满足 0 <= r < R 且 0 <= c < C。

另外,我们在该矩阵中给出了一个坐标为 (r0, c0) 的单元格。

返回矩阵中的所有单元格的坐标,并按到 (r0, c0) 的距离从最小到最大的顺序排,其中,两单元格(r1, c1) 和 (r2, c2) 之间的距离是曼哈顿距离,|r1 - r2| + |c1 - c2|。(你可以按任何满足此条件的顺序返回答案。)

 

示例 1:

输入:R = 1, C = 2, r0 = 0, c0 = 0
输出:[[0,0],[0,1]]
解释:从 (r0, c0) 到其他单元格的距离为:[0,1]
示例 2:

输入:R = 2, C = 2, r0 = 0, c0 = 1
输出:[[0,1],[0,0],[1,1],[1,0]]
解释:从 (r0, c0) 到其他单元格的距离为:[0,1,1,2]
[[0,1],[1,1],[0,0],[1,0]] 也会被视作正确答案。
示例 3:

输入:R = 2, C = 3, r0 = 1, c0 = 2
输出:[[1,2],[0,2],[1,1],[0,1],[1,0],[0,0]]
解释:从 (r0, c0) 到其他单元格的距离为:[0,1,1,2,2,3]
其他满足题目要求的答案也会被视为正确,例如 [[1,2],[1,1],[0,2],[1,0],[0,1],[0,0]]。
 

提示:

1 <= R <= 100
1 <= C <= 100
0 <= r0 < R
0 <= c0 < C

 

解法一:直接排序数组

显然,最暴力的解法是按照距离排序,然后依次输出坐标。

注意:

本解法可以使用哈希表优化,即使用坐标作 key,使用距离作 value,然后按照距离排序,这样就不会因为多次对同一下标进行比较而重复计算距离
无论如何优化,核心仍然是直接排序,时间复杂度不会优于 O(RClog(R*C))

 

代码:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;

struct node{
    int x;
    int y;
    int distance;
};
node  arr[100];
int cmp(const node& a,const node& b){
    return a.distance<b.distance;
}
int main(){
    int R,C,t=0;
    cin>>R>>C;
    int r0,c0;
    cin>>r0>>c0;
    for (int i = 0; i < R; i++)
    {
        for (int j = 0; j < C; j++)
        {
            arr[t].x=i;
            arr[t].y=j;
            arr[t++].distance=abs(i-r0)+abs(j-c0);
        }
    }
    sort(arr,arr+t,cmp);
    for (int i = 0; i < t; i++)
    {
        cout<<"x= "<<arr[i].x<<" y= "<<arr[i].y<<" distance= "<<arr[i].distance<<endl; 
    }
}

输入:

2 3
1 2

输出:

x= 1 y= 2 distance= 0
x= 0 y= 2 distance= 1
x= 1 y= 1 distance= 1
x= 0 y= 1 distance= 2
x= 1 y= 0 distance= 2
x= 0 y= 0 distance= 3

 

解法二:桶排序

遍历所有坐标,按照距离的大小分组,每组的距离相等(即放入一个桶中)
按照距离从小到大的原则,遍历所有桶,并输出结果
本解法关键在于求得可能的最大距离,即行距离和列距离都最大时:max(r0, R - 1 - r0) + max(c0, C - 1 - c0)

注意:

此解法时间复杂度为 O(R*C),理论上已达到最快可能
实际时间消耗会比预估要差,不同语言便利程度和优化不一,原因如下:
桶的制作涉及大量容器的初始化和存取
桶中要存储大量的坐标信息,不论是直接使用长度为 2 的小数组存储,还是用新的简单数据类,都会耗费很多时间

 

vector<vector<int> > allCellsDistOrder(int R, int C, int r0, int c0) {
    vector<vector<int> > ans(R * C, vector<int>(2, 0));
    vector<vector<int> > m(R + C);
    for (int i = 0; i < R; ++i) 
        for (int j = 0; j < C; ++j) {
            int dis = abs(i - r0) + abs(j - c0);
            m[dis].push_back(i);
            m[dis].push_back(j);
        }

    // for (int i = 0; i < R + C; ++i) {
    //     for (int j = 0; j < m[i].size(); j += 2)
    //     {
    //         cout<<m[i][j]<<" "<<m[i][j+1]<<endl;
    //     }
    // }  
    int cnt = 0;
    for (int i = 0; i < R + C; ++i) {
        for (int j = 0; j < m[i].size(); j += 2)

        {
            ans[cnt][0] = m[i][j];
            ans[cnt][1] = m[i][j + 1];
            ++cnt;
        }
    }
    // for (int i = 0; i < cnt; i++)
    // {
    //     cout<<ans[i][0]<<" "<<ans[i][1]<<endl;
    // }
    return ans;
}

解法三:BFS

可以把所有的坐标看作树的结点,距离相等的结点位于树的同一层
而对于每一层的结点,它们的距离 dist 可以分为行距离和列距离,且 rowDist + colDist = dist 必然成立
使 rowDist 从 0 到 dist 递增,colDist 相应有不同的值,可以得到不同的坐标:
横坐标为:r0 - rowDist 或 r0 + rowDist
纵坐标为:c0 - colDist 或 c0 + colDist
注意特殊情况:rowDist 或 colDist 为 0 时,每组只有一个正确值
对步骤 3 中,所有在矩阵范围内的坐标进行记录
注意:

此解法不关心最大距离,只要步骤 4 中记录的结果达到 R * C 的数量就可以终止搜索
此解法的时间复杂度是 O((R+C)^2),因为对每一种距离 dist,rowDist 都要进行从 0 开始递增到 dist 的遍历操作,而距离可能的最大值为 R + C
此解法时间复杂度大于 O(R * C) 的原因是:每种距离可能产生多个不在矩阵内的坐标,但搜索算法必须依次检查予以排除
理论上此解法并不比桶排序优秀,但是代码中极少创建额外的容器和对象,所以实际的运行效率不会太差

class Solution {
    public int[][] allCellsDistOrder(int R, int C, int r0, int c0) {
        int[][] re = new int[R * C][2];
        int dist = 0;
        int cnt = 0;
        int[] factor = {-1, 1};
        while (cnt < R * C) {
            for (int rowDist = 0; rowDist <= dist; rowDist++) {
                int colDist = dist - rowDist;
                for (int i = 0; i < 2; i++) {
                    int row = r0 + factor[i] * rowDist;
                    for (int j = 0; j < 2; j++) {
                        int col = c0 + factor[j] * colDist;
                        if (row >= 0 && row < R && col >= 0 && col < C) {
                            re[cnt][0] = row;
                            re[cnt][1] = col;
                            cnt++;
                        }
                        if (colDist == 0) break;
                    }
                    if (rowDist == 0) break;
                }
            }
            dist++;
        }

        return re;
    }
}

解法四:几何法(类 BFS)


如果把矩阵当作二维直角坐标系中的图形,而且把所有不在矩阵内的点也考虑进来,那么所有到 (r0, c0) 点的“距离”相等的整数坐标有明显的规律:


可以看到,它们的坐标都在一个正方形的边上(包括顶点),而且正方形的上下顶点 row 值为 r0,左右顶点 col 值为 c0。
这样,只要保证每次找到一个正方形的顶点,然后按照规律“画出”这个正方形即可,画图步骤如下:

保存 4 个向量标明画线的方向
出发点为 (r0 - 1, c0)
按照 1 中的向量指示方向画线,遇到一个正方形的顶点就更换为下一个向量(向左转 90°)
在上述的画线步骤中,不断检查线上的整数坐标,如果符合要求就进行记录。

注意:

顶点的判断方法有两组,分别对应和 r0 或 c0 是否相等
对每个距离 dist 都要画出正方形检查,检查的点数量是 8 * dist,而最大距离可能是 R + C,所以时间复杂度为 O((R+C)^2)
此解法代码中看似没有按照距离分层遍历,实际每个初始顶点的求解过程中已经包含了按照距离分层的想法,实际极其类似 BFS
此解法要检查的点理论上多于 BFS,尤其是 (r0, c0) 位于矩阵一角时会明显偏慢(最后要画很多很大的正方形)

class Solution {
    public int[][] allCellsDistOrder(int R, int C, int r0, int c0) {
        int[][] re = new int[R * C][2];
        re[0][0] = r0;
        re[0][1] = c0;
        int[] dr = {1, 1, -1, -1};
        int[] dc = {1, -1, -1, 1};
        int row = r0;
        int col = c0;
        var cnt = 1;
        while (cnt < R * C) {
            row--;
            for (int i = 0; i < 4; i++) {
                while ((i % 2 == 0 && row != r0) || (i % 2 != 0 && col != c0)) {
                    if (row >= 0 && row < R && col >= 0 && col < C) {
                        re[cnt][0] = row;
                        re[cnt][1] = col;
                        cnt++;
                    }
                    row += dr[i];
                    col += dc[i];
                }
            }
        }
        return re;
    }
}

 

posted @ 2020-08-09 12:30  关注我更新论文解读  阅读(78)  评论(0编辑  收藏  举报