矩阵问题
问题一:转圈打印矩阵
给定一个整型矩阵matrix,请按照转圈的方式打印它。
要求:额外空间复杂度为O(1)。
例如: [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ] 打印结果为: 1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10
本题可以在 LeetCode 54 螺旋矩阵测试结果。
思路
定义一个方法,以左上角的点和右下角的点开始,从边界开始打印矩形。打印一圈后循环遍历内圈的矩形,直到整个矩形打印完毕。有一些特殊的情况,比如打印的是一条竖线,或者横线,或者一个点,都没有关系,注意代码的边界即可。
实现
public static void spiralOrderPrint(int[][] matrix, int tR, int tC, int dR, int dC) { //三种情况:横线,竖线,矩形 if (tR == dR) { for (int i = tC; i <= dC; i++) { System.out.printf("%3d", matrix[tR][i]); } } else if (tC == dC) { for (int i = tR; i <= dR; i++) { System.out.printf("%3d", matrix[i][tC]); } } else { int tmpC = tC; int tmpR = tR; while (tmpC < dC) { System.out.printf("%3d", matrix[tR][tmpC++]); } while (tmpR < dR) { System.out.printf("%3d", matrix[tmpR++][dC]); } while (tC < tmpC) { System.out.printf("%3d", matrix[dR][tmpC--]); } while (tR < tmpR) { System.out.printf("%3d", matrix[tmpR--][tC]); } } } public static void printMatrix(int[][] matrix) { System.out.println("-----------PrintMatrixInSpiralOrder.printMatrix-----------"); int tR = 0; int tC = 0; int dR = matrix.length - 1; int dC = matrix[0].length - 1; //横线,竖线等特殊形式都囊括在内 while (tR <= dR && tC <= dC) { spiralOrderPrint(matrix, tR++, tC++, dR--, dC--); } }
测试
public static void main(String[] args) { int[][] matrix = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16} }; for (int[] arr : matrix) { for (int n : arr) { System.out.printf("%3d", n); } System.out.println(); } printMatrix(matrix); }
output: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 -----------PrintMatrixInSpiralOrder.printMatrix----------- 1 2 3 4 8 12 16 15 14 13 9 5 6 7 11 10
问题二:旋转正方形矩阵
给定一个整型正方形矩阵 matrix,请把该矩阵调整成逆时针旋转 90 度的样子。
要求:额外空间复杂度为O(1)。
例如: [ 1 2 3 4 5 6 7 8 9 ] 旋转: [ 3 6 9 2 5 8 1 4 7 ]
思路
先从外圈开始旋转,然后依次对内一圈一圈转,直到转完为止。
实现
public static void rotateMatrix(int[][] matrix) { int tR = 0; int tC = 0; int dR = matrix.length - 1; int dC = matrix[0].length - 1; //当两个点重合时,旋转就结束了 while (tR < dR) { rotate(matrix, tR++, tC++, dR--, dC--); } } public static void rotate(int[][] matrix, int tR, int tC, int dR, int dC) { int times = dR - tR; for (int i = 0; i < times; i++) { //四个点变换位置 int tmp = matrix[tR + i][tC]; matrix[tR + i][tC] = matrix[tR][dC - i]; matrix[tR][dC - i] = matrix[dR - i][dC]; matrix[dR - i][dC] = matrix[dR][tC + i]; matrix[dR][tC + i] = tmp; } }
测试
public static void main(String[] args) { //int[][] matrix = { // {1, 2, 3}, // {4, 5, 6}, // {7, 8, 9}, //}; int[][] matrix = { {1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}, {11, 12, 13, 14, 15}, {16, 17, 18, 19, 20}, {21, 22, 23, 24, 25} }; printMatrix(matrix); rotateMatrix(matrix); printMatrix(matrix); } public static void printMatrix(int[][] matrix) { System.out.println("------------RotateMatrix.printMatrix------------"); for (int[] arr : matrix) { for (int n : arr) { System.out.printf("%3d", n); } System.out.println(); } System.out.println(); }
使用了两组数做测试,结果完成旋转,输出如下:
------------RotateMatrix.printMatrix------------ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ------------RotateMatrix.printMatrix------------ 5 10 15 20 25 4 9 14 19 24 3 8 13 18 23 2 7 12 17 22 1 6 11 16 21
小总结
问题一和问题二都是矩阵的调整打印问题,采用的思路都很相似,从外围出发,一圈一圈向中心进攻。不要一直着眼在细节处,从宏观的角度浓缩出规律,再在细节处下功夫,如坐标的变换也是重点。这是一种比较通用的方法,把复杂的坐标变换浓缩成规律的活动,多训练这种思考方式,才能更便捷的解决问题。
问题三:“之”字形打印矩阵
给定一个矩阵matrix,按照“之”字形的方式打印这个矩阵,例如:
[ 1 2 3 4 5 6 7 8 9 10 11 12 ]
“之”字形打印的结果为:1 2 5 9 6 3 4 7 10 11 8 12
要求:额外空间复杂度为O(1)。
思路
从左上角 matrix[0][0]
位置引出两点 A、B,点 A 运动规则是向右走,遇到边界向下走,点 B 运动规则是向下走,遇到边界向右走。每走一步,两点形成的路径就是“之”字的某一条路径。再设定一个 flag 变量,来表示从 A -> B 的路径和从 B -> A 的路径。
实现
public static void zigPrint(int[][] matrix) { int row = matrix.length - 1; int col = matrix[0].length - 1; // A int tR = 0; int tC = 0; // B int dR = 0; int dC = 0; boolean flag = false; //true A -> B false B -> A while (tR != row + 1) { print(matrix, tR, tC, dR, dC, flag); //点 A 判断条件是 tC,所以 tC 放在 tR 后,防止 tC 值的改变对 tR 值的判断造成影响 tR = tC == col ? tR + 1 : tR; tC = tC == col ? tC : tC + 1; //点 B 判断条件是 dR,所以 dR 放在 dC 后,防止 dR 值的改变对 dC 值的判断造成影响 dC = dR == row ? dC + 1 : dC; dR = dR == row ? dR : dR + 1; flag = !flag; } } private static void print(int[][] matrix, int tR, int tC, int dR, int dC, boolean flag) { if (flag) { // true A -> B while (tR != dR + 1) { System.out.printf("%3d", matrix[tR++][tC--]); } } else { // false B -> A while (dR != tR - 1) { System.out.printf("%3d", matrix[dR--][dC++]); } } }
测试
public static void main(String[] args) { int[][] matrix = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, }; zigPrint(matrix); }
output: 1 2 5 9 6 3 4 7 10 11 8 12
小总结
在解题过程中,能使用基础数据类型一定要使用,因为其传值的特性有助于限制变量的作用域。当其作用域可控,算法就更容易理解。
在运算过程中,这个部分需要注意,前后顺序不同结果会受到影响,注意运算的逻辑:
//点 A 判断条件是 tC,所以 tC 放在 tR 后,防止 tC 值的改变对 tR 值的判断造成影响 tR = tC == col ? tR + 1 : tR; tC = tC == col ? tC : tC + 1; //点 B 判断条件是 dR,所以 dR 放在 dC 后,防止 dR 值的改变对 dC 值的判断造成影响 dC = dR == row ? dC + 1 : dC; dR = dR == row ? dR : dR + 1;
问题四:在行列都排好序的矩阵中找数
给定一个有 N * M 的整型矩阵 matrix 和一个整数 K,matrix 的每一行和每一 列都是排好序的。实现一个函数,判断 K 是否在 matrix 中。
例如:
[ 0 1 2 5 2 3 4 7 4 5 7 9 ]
如果 K 为7,返回 true;如果 K 为6,返回 false。
要求:时间复杂度为 O(N + M),额外空间复杂度为 O(1)。
思路
选择右上角的数 a 比较 > a 排除 a 左侧的数,往下寻找 < a 排除 a 下方的数,往左寻找 按照这个策略比较,最多只需要遍历 N + M 个数就可以知道是否找到数
实现
public static boolean findNumber(int[][] matrix, int number) { int dR = matrix.length - 1; int dC = 0; int tR = 0; int tC = matrix[0].length - 1; while (tR <= dR && tC >= dC) { if (matrix[tR][tC] == number) { return true; } if (matrix[tR][tC] < number) { tR++; } else { tC--; } } return false; }
测试
public static void main(String[] args) { int[][] matrix = { {1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}, {11, 12, 13, 14, 15}, {16, 17, 18, 19, 20}, {21, 22, 23, 24, 25} }; for (int i = 0; i < 10000; i++) { int number = (int) (Math.random() * 100); System.out.println("number = " + number); System.out.println(findNumber(matrix, number)); } }