算法 - 矩阵问题
矩阵问题
问题一:转圈打印矩阵
给定一个整型矩阵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));
}
}