遍历二维矩阵的各种花样
PS:干货很多,建议分多次食用
常规遍历
# 输出以下序列: [1 2 3 4...15 16]
这个就是按行遍历即可
示例代码
int len = matrix.length;
for (int i = 0; i < len; i++) {
for (int j = 0; j < len; j++) {
System.out.print(matrix[i][j]+" ");
}
}
旋转遍历
PS:本遍历方法使用生成以下矩阵来讲述,生成会了,遍历自然也会了
示意图
遍历思路
# [0,0] [0,1] [0,2] [0,3] 👉向右
# [0,4] [1,4] [2,4] [3,4] 👇向下
# [4,4] [4,3] [4,2] [4,1] 👆向上
# [4,0] [3,0] [2,0] [1,0] 👈向左
# ......
圈圈法
第一个思路就是一圈一圈的遍历
将每一个圈拆为四个部分
第一个循环用来生成水平向左的数据
第二个循环用来生成垂直向下的数据
第三个循环用来生成水平向右的数据
第四个循环用来生成垂直向上的数据
流程图
粉红色的是标注为每次大循环的开始处
PS:图片很多,过程很详细
示例代码
public int[][] rotateNumber(int n) {
int[][] matrix = new int[n][n];
int counter = 1;
// n / 2即这个矩阵有几个圈
for (int i = 0; i < n / 2; i++) {
// end即本次这个圈的宽-1
int end = n - i - 1;
// →
for (int j = i; j < end; j++) {
matrix[i][j] = counter++;
}
// ↓
for (int j = i; j < end; j++) {
matrix[j][end] = counter++;
}
// ←
for (int j = end; j > i; j--) {
matrix[end][j] = counter++;
}
// ↑
for (int j = end; j > i; j--) {
matrix[j][i] = counter++;
}
}
// 如果是奇数,单独处理中间那一格数据
if (n % 2 != 0) {
matrix[n / 2][n / 2] = n * n;
}
return matrix;
}
横平竖直法(推荐)
这种方法是通过四个边界来辅助生成矩阵
四个边界的示意图
示例代码
public int[][] rotateNumber(int n){
// 用来存放生成的旋转数字矩阵
int[][] matrix = new int[n][n];
// 计数器
int counter = 1;
// 上边界
int up = 0;
// 下边界
int down = n-1;
// 左边界
int left = 0;
// 右边界
int right = n-1;
// 当 counter 比 n * n 小就一直循环
while (counter <= n * n) {
// 遍历上边界的那一行
for (int j = left; j <= right; j++) {
matrix[up][j] = counter++;
}
// 上边界向下移动一行
up++;
// 遍历右边界的那一行
for (int i = up; i <= down; i++) {
matrix[i][right] = counter++;
}
// 右边界向左移动一行
right--;
// 遍历下边界的那一行
for (int j = right; j >= left; j--) {
matrix[down][j] = counter++;
}
// 下边界向上移动一行
down--;
// 遍历左边界的那一行
for (int i = down; i >= up; i--) {
matrix[i][left] = counter++;
}
// 左边界向右移动那一行
left++;
}
// 返回结果数组
return matrix;
}
斜遍历
# 输出所有由左上向右下方向的对角线: [13 9 14 5 10 15 1 6 11 16 2 7 12 3 8 4]
偏移X法
和往常一样遍历第一行,[0,0] [0,1] [0,2] [0,3] [0,4]
可以发现中间那个对角线是这一行向下偏移的结果,但是不同的位置在 X 方向上的偏移量是不一样的
遍历第二行[1,0] [1,1] [1,2] [1,3] [1,4]
可以看到和刚刚第一行一样,每一列向下偏移一定量就可以得到主对角线下面的那个对角线了
(这里先不要管越界的问题,先把思路理清楚,再在代码里面解决越界问题)
先写出遍历每一行的初始代码,然后我们在这个上面改造
for (int i = 0; i < len; i++) {
for (int j = 0; j < len; j++) {
System.out.println(matrix[i][j]);
}
}
在遍历每一行的情况,向下偏移就是对角线了
每一列的偏移量都是不一样的,接下来要解决的就是如何算出每一列的偏移量
经过观察可以发现,当前这一列的偏移量就是当前的列值,那么就好办了直接用j
就可以了
for (int i = 0; i < len; i++) {
for (int j = 0; j < len; j++) {
System.out.println(matrix[i + j][j]);
}
}
现在还有一个问题,就是得到的对角线是部分不合法的,那么接下来就是如何截取合法的对角线部分
因为对角线是由每一行向下偏移而来,对角线越界的根本原因是每一行遍历多了
那么只要逐渐减少每一行的遍历长度即可,每一行少遍历 i 的长度即可
for (int i = 0; i < len; i++) {
for (int j = 0; j < len - i; j++) {
System.out.println(matrix[i + j][j]);
}
}
完整示例代码
PS:注意右上角
for (int i = len - 1; i >= 0; i--) {
for (int j = 0; j < len - i; j++) {
System.out.println(matrix[i + j][j]);
}
}
for (int i = len - 2; i >= 0; i--) {
for (int j = len - 1 - i; j < len; j++) {
// 偏移量
int offset = len - 1 - j;
System.out.println(matrix[i - offset][j]);
}
}
偏移Y法
这个方法和偏移 X 法有异曲同工之妙
只不过从遍历每一行变成了遍历每一列
和刚刚一样,先写出遍历每一列
for (int j = 0; j < len; j++) {
for (int i = 0; i < len; i++) {
System.out.println(matrix[i][j]);
}
}
然后向右边偏移
for (int j = 0; j < len; j++) {
for (int i = 0; i < len; i++) {
System.out.println(matrix[i][j + i]);
}
}
对角线不合法,截取合法部分
for (int j = 0; j < len; j++) {
for (int i = 0; i < len - j; i++) {
System.out.println(matrix[i][j + i]);
}
}
完整示例代码
PS:注意左下角
for (int j = 0; j < len; j++) {
for (int i = len - 1 - j; i < len; i++) {
int offset = len - 1 - i;
System.out.println(matrix[i][j - offset]);
}
}
for (int j = 1; j < len; j++) {
for (int i = 0; i < len - j; i++) {
System.out.println(matrix[i][i + j]);
}
}
偏移对角线法(推荐)
可以发现偏移 X 法和偏移 Y 法都比较麻烦
所以我比较推荐这种方法,根据对角线来做偏移就会简单很多
从左上顶角到右下顶角的那条对角线如下[0,0] [1,1] [2,2] [3,3]
@表示越界舍弃
整体往下偏移 1 位,就是[1,0] [2,1] [3,2] [4,3](@)
整体往下偏移 2 位,就是[2,0] [3,1] [4,2](@) [5,3](@)
整体往下偏移 3 位,就是[3,0] [4,1] [5,2](@) [6,3](@)
整体往上偏移 1 位,就是[-1,0](@) [0,1] [1,2] [2,3]
整体往上偏移 2 位,就是[-2,0](@) [-1,1](@) [-1,0][0,2] [1,3]
整体往上偏移 3 位,就是[-3,0](@) [-2,1](@) [-1,2](@) [0,3]
如何解决越界
既然完整的对角线偏移会出现越界,那就偏移部分的对角线不就行了
int len = matrix.length;
// 输出部分对角线,从完整开始逐渐变短
for (int offset = 0; offset < len; offset++) {
for (int i = 0; i < len - offset; i++) {
System.out.println(matrix[i][i]);
}
}
// 这时候输出的依次是
// [0,0] [1,1] [2,2] [3,3] (offset=0)
// [0,0] [1,1] [2,2] (offset=1)
// [0,0] [1,1] (offset=2)
// [0,0] (offset=3)
// 那么只需要根据部分的对角线向下偏移即可
for (int offset = 0; offset < len; offset++) {
for (int i = 0; i < len - offset; i++) {
System.out.println(matrix[i + offset][i]);
}
}
// 这样的输出结果就是
// [0,0] [1,1] [2,2] [3,3] (offset=0)
// [1,0] [2,1] [3,2] (offset=1)
// [2,0] [3,1] (offset=2)
// [3,0] (offset=3)
在理解了上述的方法后,修改下来完成第一部分的遍历,即这一块的遍历
这个和刚刚不同的地方在于它的偏移量是从大到小的,上面那个理解了,这个应该也不难
// [3,0]
// [2,0] [3,1]
// [1,0] [2,1] [3,2]
// [0,0] [1,1] [2,2] [3,3]
// offset是对角线在x方向的偏移量
for (int offset = len - 1; offset >= 0; offset--) {
for (int i = 0; i < len - offset; i++) {
// 向下偏移
System.out.println(matrix[i + offset][i]);
}
}
完整示例代码
public void tiltPrint(int[][] matrix){
int len = matrix.length;
for (int offset = len - 1; offset >= 0; offset--) {
for (int i = 0; i < len - offset; i++) {
// 向下偏移
System.out.println(matrix[i + offset][i]);
}
}
for (int offset = 1; offset < len; offset++) {
for (int i = offset; i < len; i++) {
// 向上偏移
System.out.println(matrix[i - offset][i]);
}
}
}
螺旋遍历
# 输出螺旋遍历顺序: [1 2 3 4 5 6 7 8 9 10 11 ... 21]
拆解法
将这个路径拆解开
拆解如下
# [0,0] -- [0,1] [1,0]
# [2,0] [1,1] [0,2] --- [0,3] [1,2] [2,1] [3,0]
# [4,0] [3,1] [2,2] [1,3] [0,4] --- [0,5] [1,4] [2,3] [3,2] [4,1] [5,0]
流程图
粉红色的是标注为每次大循环的开始处
标注内层循环初始化的是该循环刚要开始时候的数据
PS:图片很多,过程很详细
示例代码
// i记录了次数和每一次输出开始的点的row
// 0 2 4
for (int i = 0; i < matrix.length; i+=2) {
// 第一个输出的数字的column坐标开头都是0
// [0,0][2,0][4,0]
int j = 0;
// 第一个内循环是左下角往右上角方向的
// k(row)从i自减
// j(column)自增到row到0
for (int k = i; k >= 0; k--) {
System.out.print(matrix[k][j++]+" ");
}
// 记录结束位置
int end=j;
// 如果n是奇数,在最后一次大循环中就不会有这个小循环了
if(j >= matrix.length){
break;
}
// 第二个内循环是右上角往左下角方向的
// k(row)从0开始自增到end
// j(column)自减到k到end
for (int k = 0; k <= end; k++) {
System.out.print(matrix[k][j--]+" ");
}
}
连续法
把这些路径当作是连续的
维护一个[rowIndex,columnIndex]
来走这一条连续的路径
示例代码
int row = matrix.length;
int column = matrix[0].length;
int whole = row * column;
int counter = 0;
int rowIndex = 0;
int columnIndex = 0;
// 只要计数器比一半少,就说明还没走完
while (counter < whole / 2) {
while (rowIndex >= 0) {
System.out.println(matrix[rowIndex--][columnIndex++]);
counter++;
}
// 复位
rowIndex = 0;
while (columnIndex >= 0) {
System.out.println(matrix[rowIndex++][columnIndex--]);
counter++;
}
// 复位
columnIndex = 0;
}