题目:给出 R 行 C 列的矩阵,其中的单元格的整数坐标为 (r, c),满足 0 <= r < R 且 0 <= c < C。
另外,我们在该矩阵中给出了一个坐标为 (r0, c0) 的单元格。
返回矩阵中的所有单元格的坐标,并按到 (r0, c0) 的距离从最小到最大的顺序排,其中,两单元格(r1, c1) 和 (r2, c2) 之间的距离是曼哈顿距离,|r1 - r2| + |c1 - c2|。(你可以按任何满足此条件的顺序返回答案。)
注意:曼哈顿距离并不是所谓的两点之间的距离,其计算公式为:|x1 - x2| + |y1 - y2|
示例:
方法一:直接排序
class Solution {
public int[][] allCellsDistOrder(int R, int C, int r0, int c0) {
int[][] res=new int[R*C][2];
int k=0;
for(int i=0;i<R;i++) {
for(int j=0;j<C;j++,k++) {
res[k][0]=i;
res[k][1]=j;
}
}
Arrays.sort(res,(o1,o2)->(Math.abs(o1[0]-r0)+Math.abs(o1[1]-c0))-(Math.abs(o2[0]-r0)+Math.abs(o2[1]-c0)));
return res;
}
}
方法二:桶排序
注意到方法一中排序的时间复杂度太高。实际在枚举所有点时,我们可以直接按照哈曼顿距离分桶。这样我们就可以实现线性的桶排序
class Solution {
public int[][] allCellsDistOrder(int R, int C, int r0, int c0) {
int maxDist = Math.max(r0, R - 1 - r0) + Math.max(c0, C - 1 - c0);
List<List<int[]>> bucket = new ArrayList<List<int[]>>();
for (int i = 0; i <= maxDist; i++) {//距离r0,c0的距离从0到maxDist共maxDist+1个桶
bucket.add(new ArrayList<int[]>());
}
for (int i = 0; i < R; i++) {
for (int j = 0; j < C; j++) {
int d = dist(i, j, r0, c0);
bucket.get(d).add(new int[]{i, j});//加入相应距离大小的桶里面
}
}
int[][] ret = new int[R * C][];
int index = 0;
for (int i = 0; i <= maxDist; i++) {
for (int[] it : bucket.get(i)) {
ret[index++] = it;
}
}
return ret;
}
public int dist(int r1, int c1, int r2, int c2) {
return Math.abs(r1 - r2) + Math.abs(c1 - c2);
}
}
方法三:几何法(类BFS)
思路及解法
我们也可以直接变换枚举矩阵的顺序,直接按照曼哈顿距离遍历该矩形即可。
注意到曼哈顿距离相同的位置恰好构成一个斜着的正方形边框,因此我们可以从小到大枚举曼哈顿距离,并使用循环来直接枚举该距离对应的边框。我们每次从该正方形边框的上顶点出发,依次经过右顶点、下顶点和左顶点,最后回到上顶点。这样即可完成当前层的遍历
class Solution {
int[] dr = {1, 1, -1, -1};
int[] dc = {1, -1, -1, 1};
public int[][] allCellsDistOrder(int R, int C, int r0, int c0) {
int maxDist = Math.max(r0, R - 1 - r0) + Math.max(c0, C - 1 - c0);
int[][] ret = new int[R * C][];
int row = r0, col = c0;
int index = 0;
ret[index++] = new int[]{row, col};
for (int dist = 1; dist <= maxDist; dist++) {
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) {
ret[index++] = new int[]{row, col};
}
row += dr[i];
col += dc[i];
}
}
}
return ret;
}
}
方法四: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;
}
}
方法五:stream
class Solution {
public int[][] allCellsDistOrder(int R, int C, int r0, int c0) {
return IntStream.range(0, R).boxed()
.flatMap(r -> IntStream.range(0, C).mapToObj(c -> new int[]{r, c}))
.sorted((pos1, pos2) -> dist(pos1[0], pos1[1], r0, c0) - dist(pos2[0], pos2[1], r0, c0))
.toArray(int[][]::new);
}
private int dist(int r, int c, int r0, int c0) {
return Math.abs(r - r0) + Math.abs(c - c0);
}
}