多维数据和矩阵(力扣)
矩阵
螺旋矩阵
题号54, 参考资料
给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。
示例 1:
请在这里输入引用内容
输入:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
输出: [1,2,3,6,9,8,7,4,5]
原问题就是希望我们能顺时针转圈打印矩阵:
把抽象的问题转化成简单的,是这个题的精髓。如果我们将眼光局限于坐标每次该如何移动,如何判断矩阵中哪些点已经输出,哪些点还没有输出,那么你就是进坑里了,这种方法不是不行,但是在面试的场景下,扣各种边界会导致你非常容易出错。那么有什么办法可以快速解决呢?其实很简单,从全局来看,我们实际上是在绘制一个又一个的矩形边界:
如何绘制本层边界?如下图所示,我们每次绘制某一边界时(如矩形的上边界),不要把最后一个点绘制了,而是作为下一个边界的起点,那么最终结束位置在5,与下一圈的6正好衔接:
内外层衔接?
我们只需控制左上角和右下角两个点的坐标,然后每跑完一圈,坐标进行相应的增加或减少即可:
要考虑两个边界情况,一行和一列
public static void printMatrix(int[][] matrix){ // 设置对角点 if (matrix == null){ return; } // 两个对角点(a,b) 和 (c,d) int a = 0; int b = 0; int c = matrix.length - 1; int d = matrix[0].length - 1; // 控制对角点向中心移动 while ( a<=c && b<=d) { // 绘制轮廓函数 printContour(a++, b++, c--, d--, matrix); } } public static void printContour(int a, int b, int c, int d, int[][] matrix){ // 一个点包含在一行和一列的情况中了 // 边界情况一:一行 if (a == c){ for (int j = b; j <= d; ++j){ System.out.print(matrix[a][j] + " "); } }else if (b == d){ // 边界情况二:一列 for (int i = a; i <= c; ++i){ System.out.print(matrix[i][b] + " "); } }else{ // up side for (int j = b; j < d; ++j){ System.out.print(matrix[a][j] + " "); } // right side for (int i = a; i < c; ++i){ System.out.print(matrix[i][d] + " "); } // bottom side for (int j = d; j > b; --j){ System.out.print(matrix[c][j] + " "); } // left side for (int i = c; i > a; --i){ System.out.print(matrix[i][b] + " "); } } }
螺旋矩阵2
题号59
给定一个正整数 n,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
输入: 3
输出:
[
[ 1, 2, 3 ],
[ 8, 9, 4 ],
[ 7, 6, 5 ]
]
继承与螺旋矩阵,这个题的思路就很清晰了,控制对角点打印并填充到list中,最后注意中心点要单独打印,这个是边界情况。
public static int[][] generateMatrix(int n){ // 因为是方阵,所以只需要控制两个对角点向中心移动,并填充轮廓 int[][] matrix = new int[n][n]; int a = 0; int b = 0; int c = matrix.length - 1; int d = matrix[0].length - 1; int num = 0; // 不停的绘制轮廓,方法和 spiral_matrix_i 一致 while( a<=c && b<=d){ // top side for (int j = b; j < d; ++j){ matrix[a][j] = ++num; } // right side for (int i = a; i < c; ++i){ matrix[i][d] = ++num; } // bottom side for (int j = d; j > b; --j){ matrix[c][j] = ++num; } // left side for (int i = c; i > a; --i){ matrix[i][b] = ++num; } // central point if ( a == c && b == d){ matrix[a][b] = ++ num; } ++a; ++b; --d; --c; } return matrix; }
图像旋转
力扣48
给定一个 n × n 的二维矩阵表示一个图像。
将图像顺时针旋转 90 度。
说明:
你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
解题思路:
首先观察旋转后的图像:
按照之前的思路,把问题分解,先观察最外面一圈的变化,问题就转换成如何将一个矩形边界旋转的问题了:
public static void rotate(int[][] matrix){ // 设置两个对角点 int tR = 0; int tC = 0; int dR = matrix.length-1; int dC = matrix[0].length-1; // 由外层到内层,逐步分解问题, 最后一个点不用处理 while (tR < dR){ rotateEdge(tR++, tC++, dR--, dC--, matrix); } }
我们先考虑最外面一层的第一个点,再考虑第二个点:
观察发现,这个跟遍历某一个边界的一边非常像,我们原地交换四个点只需要5步,再加上遍历某一边,旋转一个边界就此完成。
在交换的时候注意,如果按照顺时针顺序就会把前面的值覆盖了,如果采用逆时针,先记录1
的值,然后让13
覆盖它,再让16
覆盖13
,然后让4
覆盖16
,最后让temp
覆盖4
,按照这个顺序就只需要记录一个临时值。
public static void rotate(int[][] matrix){ // 设置两个对角点 int tR = 0; int tC = 0; int dR = matrix.length-1; int dC = matrix[0].length-1; // 由外层到内层,逐步分解问题, 最后一个点不用处理 while (tR < dR){ rotateEdge(tR++, tC++, dR--, dC--, matrix); } } /* (tR,tC) * * (tR,dC) * * * * * * * * (dR,tC) * * (dR,dC) 第一次的时候: 0. 左上角的数(tR, tC), 用temp代理 1. 左上角(tR, tC) <-- 左下角的数(dR, tC) 2. 左下角(dR, tC) <-- 右下角的数(dR, dC) 3. 右下角(dR, dC) <-- 右上角(tR, dC) 4. 右上角(tR, dC) <-- 左上角的数代理(temp) 下一次的时候, 之后以此类推,做成循环 (tR, tC)向右移动->(tR, tC+1) (dR, tC)向上移动->(dR-1, tC) (dR, dC)向左移动->(dR, dC-1) (tR, dC)向下移动->(tR+1, dC) */ public static void rotateEdge(int tR, int tC, int dR, int dC, int[][] matrix){ // 每一边有 dC-tC+1 个点,但只交换 dC-tC 次,每次最后一个点要剔除 // 比如 tC=0, dC=3, 一边4个点,只要交换3次 for (int i = 0; i < dC-tC; i++){ // 分析的时候只看成一次,移动一个点 // 每一次都是先用 temp 记录左上角的点 int temp = matrix[tR][tC+i]; // 1. 左上角(tR, tC) <-- 左下角的数(dR, tC) matrix[tR][tC+i] = matrix[dR-i][tC]; // 2. 左下角(dR, tC) <-- 右下角的数(dR, dC) matrix[dR-i][tC] = matrix[dR][dC-i]; // 3. 右下角(dR, dC) <-- 右上角(tR, dC) matrix[dR][dC-i] = matrix[tR+i][dC]; // 4. 右上角(tR, dC) <-- 左上角的数代理(temp) matrix[tR+i][dC] = temp; } }