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

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

描述

给定四个整数 row , cols , rCenter 和 cCenter 。
有一个 rows X cols 的矩阵,你在单元格上的坐标是 (rCenter, cCenter) 。

返回矩阵中的所有单元格的坐标,并按与 (rCenter, cCenter) 的 距离 从小到大排序。你可以按 任何 满足此条件的顺序返回答案。

单元格(r1, c1) 和 (r2, c2) 之间的距离为 |r1 - r2| + |c1 - c2|

示例

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

题解

1. 直接排序

首先存储矩阵内所有的点,然后将其按照哈曼顿距离直接排序。

  • 时间复杂度: O ( rows × cols × log ⁡ ( rows × cols ) ) O(\textit{rows} \times \textit{cols} \times \log(\textit{rows} \times \textit{cols})) O(rows×cols×log(rows×cols)),存储所有点时间复杂度 O ( rows × cols ) O(\textit{rows} \times \textit{cols}) O(rows×cols),排序时间复杂度 O ( rows × cols log ⁡ ( rows × cols ) ) O(\textit{rows} \times \textit{cols} \log(\textit{rows} \times \textit{cols})) O(rows×colslog(rows×cols))

  • 空间复杂度: O ( log ⁡ ( rows × cols ) ) O(\log(\textit{rows} \times \textit{cols})) O(log(rows×cols)),即为排序需要使用的栈空间,不考虑返回值的空间占用。

class Solution {
public: 
    vector<vector<int>> allCellsDistOrder(int rows, int cols, int rCenter, int cCenter) {
        vector<vector<int>> vecs(rows*cols, vector<int>(2, 0));
        for(int i = 0; i < rows; ++i){
            for(int j = 0; j < cols; ++j){
                vecs[i*cols + j] = vector<int>{i, j};
            }
        }
        sort(vecs.begin(), vecs.end(), [=](vector<int>& a, vector<int>& b) {
            return abs(a[0] - rCenter) + abs(a[1] - cCenter) < abs(b[0] - rCenter) + abs(b[1] - cCenter);
        });
        return vecs;
    }
};

2. 桶排序

方法一中排序的时间复杂度太高。实际在枚举所有点时,我们可以直接按照哈曼顿距离分桶。这样我们就可以实现线性的桶排序。

  1. 遍历所有坐标,按照距离的大小分组,每组的距离相等(即放入一个桶中)
  2. 按照距离从小到大的原则,遍历所有桶,并输出结果

本解法关键在于求得可能的最大距离,即行距离和列距离都最大时:max(r0, R - 1 - r0) + max(c0, C - 1 - c0)

  • 时间复杂度: O ( rows × cols ) O(\textit{rows} \times \textit{cols}) O(rows×cols),存储所有点时间复杂度 O ( rows × cols ) O(\textit{rows} \times \textit{cols}) O(rows×cols),桶排序时间复杂度 O ( rows × cols ) O(\textit{rows} \times \textit{cols}) O(rows×cols)
  • 空间复杂度: O ( rows × cols ) O(\textit{rows} \times \textit{cols}) O(rows×cols),需要存储矩阵内所有点。

此解法时间复杂度为 O ( R ∗ C ) O(R*C) O(RC),理论上已达到最快可能,实际时间消耗会比预估要差,不同语言便利程度和优化不一,原因如下:

  • 桶的制作涉及大量容器的初始化和存取
  • 桶中要存储大量的坐标信息,不论是直接使用长度为 2 的小数组存储,还是用新的简单数据类,都会耗费很多时间
class Solution {
public: 
    vector<vector<int>> allCellsDistOrder(int rows, int cols, int rCenter, int cCenter) {
        int maxDist = max(cCenter - 0, cols-1 - cCenter) + max(rCenter - 0, rows-1 - rCenter);
        vector<vector<vector<int>>> bucket(maxDist + 1);        // 桶
        vector<vector<int>> ans(rows*cols);
        for(int i = 0; i < rows; ++i){
            for(int j = 0; j < cols; ++j){
                int dist = abs(i - rCenter) + abs(j - cCenter);
                bucket[dist].emplace_back(vector<int>{i ,j});   // 每个距离都分一个桶
            }
        }
        int cnt = 0;
        for(int i = 0; i <= maxDist; ++i){
            for(auto& pos : bucket[i]){
                ans[cnt] = pos;             // 合并桶数据
                ++cnt;
            }
        }
        return ans;
    }
};

3. 几何法

曼哈顿距离相同的位置恰好构成一个斜着的正方形边框,因此我们可以从小到大枚举曼哈顿距离,并使用循环来直接枚举该距离对应的边框。

我们每次从该正方形边框的上顶点出发,依次经过右顶点、下顶点和左顶点,最后回到上顶点。这样即可完成当前层的遍历。

注意正方形边框中的部分点不一定落在矩阵中,所以我们需要做好边界判断。

在这里插入图片描述

  • 顶点的判断方法有两组,分别对应和 r0 或 c0 是否相等

  • 时间复杂度: O ( ( rows + cols ) 2 ) O\big((\textit{rows}+\textit{cols})^2\big) O((rows+cols)2),我们需要遍历矩阵内所有点,同时也会遍历部分超过矩阵部分的点。在最坏情况下,给定的单元格位于矩阵的一个角,例如 (0,0),此时最大的曼哈顿距离为 rows + cols − 2 \textit{rows}+\textit{cols}-2 rows+cols2,需要遍历的点数为 2 ( rows + cols − 2 ) ( rows + cols − 1 ) + 1 2(\textit{rows}+\textit{cols}-2)(\textit{rows}+\textit{cols}-1)+1 2(rows+cols2)(rows+cols1)+1,因此时间复杂度为 O ( ( rows + cols ) 2 ) O\big((\textit{rows}+\textit{cols})^2\big) O((rows+cols)2)

  • 空间复杂度: O ( 1 ) O(1) O(1),不考虑返回值的空间占用。

class Solution {
public: 
    vector<vector<int>> allCellsDistOrder(int rows, int cols, int rCenter, int cCenter) {
        const int dr[4] = {1,  1, -1, -1};  // ↘ ↙ ↖ ↗ 四个方向
        const int dc[4] = {1, -1, -1,  1};
        int row = rCenter, col = cCenter;
        vector<vector<int>> ans(rows*cols, vector<int>(2, 0));
        ans[0][0] = row;
        ans[0][1] = col;
        int cnt = 1;
        int n = rows*cols;
        while(cnt < n){
            --row;                          // 从上顶点开始
            for(int i = 0; i < 4; ++i){     // 同一个i,即为相同的前进方向(分别先后到达上、右、下、左、上顶点)
                // ↘ i = 0 && row = rCenter 右顶点; ↙ i = 1 && col = cCenter 下顶点;
                // ↖ i = 2 && row = rCenter 左顶点; ↗ i = 3 && col = cCenter 上顶点.
                while((i % 2 == 0 && row != rCenter) || (i % 2 != 0 && col != cCenter)){    
                    if(row >= 0 && row < rows && col >= 0 && col < cols){   // 排除方格外的点
                        ans[cnt][0] = row;
                        ans[cnt][1] = col;
                        ++cnt;
                    }
                    row += dr[i];           // 按照当前方向移动
                    col += dc[i];
                }
            }
        }
        return ans;
    }
};
  • 除了通过顶点判断来切换移动方向外,还能通过当前的曼哈顿距离来得到每个方向的前进次数就是距离dist
class Solution {
public: 
    vector<vector<int>> allCellsDistOrder(int rows, int cols, int rCenter, int cCenter) {
        const int dr[4] = {1,  1, -1, -1};  // ↘ ↙ ↖ ↗ 四个方向
        const int dc[4] = {1, -1, -1,  1};
        int row = rCenter, col = cCenter;
        vector<vector<int>> ans(rows*cols, vector<int>(2, 0));
        ans[0][0] = row;
        ans[0][1] = col;
        int dist = 0;
        int cnt = 1;
        int n = rows*cols;
        while(cnt < n){
            --row;      // 从上顶点开始
            ++dist;     // 曼哈顿距离(每个方向的前进次数)                
            for(int i = 0; i < 4; ++i){     // 同一个i,即为相同的前进方向(分别先后到达上、右、下、左、上顶点)
                for(int j = 1; j <= dist; ++j){    
                    if(row >= 0 && row < rows && col >= 0 && col < cols){   // 排除方格外的点
                        ans[cnt][0] = row;
                        ans[cnt][1] = col;
                        ++cnt;
                    }
                    row += dr[i];           // 按照当前方向移动
                    col += dc[i];
                } 
            } 
        }
        return ans;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值