1、题目描述
【JZ19】输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
例如,如果输入如下4 X 4矩阵:
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.
知识点:矩阵
难度:☆
2、解题思路
2.1 递归法
假设遍历到的数字为 temp,上一个遍历到的数字为 last,下一个遍历到的数字为 next。
于是,每遍历到一个 temp ,都存在两个问题:
1、last -> temp 的方向是什么;
2、temp -> next 的方向又是什么。
比如,以题目的矩阵为例子,当遍历到数字 3 时,last -> temp 为从左向右;temp -> next 也是从左向右。当遍历到数字 13 时,last -> temp 为从右向左,temp -> next 为从下向上。
因此,我们可以用递归的方式解决顺时针打印矩阵的问题。
我们能够事先直到 last -> temp 的方向,然后根据情况分析处 temp -> next 的方向即可。比如一开始第一个元素肯定是从左向右(顺时针)。
定义一个大小和输入矩阵一样大小的二维矩阵 mark[][]
用于标记位置走过的痕迹,如果位置走过,则值为 1,否则为 0。当 mark 都标记为 1 ,说明已经顺时针遍历完毕。
以前行方向分别记上下左右为u、d、l、r
。
以u
为例,当 last -> temp 为u
时,说明现在是往上走的趋势,那么下一步可能往上走,也可能往右走,也可能无路可走。
往上走的情况:当前没有到达上边界,往上走一个的位置没走过;
往右走的情况:当前在上边界,往右走一个的位置没走过;
无路可走:当前在上边界,右边走一个的位置已经走过了。
其他三个情况以此类推进行分析。
有路可走则继续递归,无路可走则直接 return;
2.2 层遍历法
其实,顺时针遍历矩阵,可以看成是从外往里一层一层遍历。先顺时针遍历最外层,然后顺时针遍历第二层,直到最后一层。
每一层分成下图四步:
我们可以用矩阵的左上角和右下角唯一表示一个矩阵。设左上角坐标为 (lx,ly) , 右下角坐标为 (rx,ry)。
3、解题代码
3.1 递归法
package pers.klb.jzoffer.hard;
import java.util.ArrayList;
/**
* @program: JzOffer2021
* @description: 顺时针打印矩阵(递归法)
* @author: Meumax
* @create: 2020-08-11 19:32
**/
public class ClockwisePrint {
public ArrayList<Integer> printMatrix(int[][] matrix) {
ArrayList<Integer> list = new ArrayList<>();
if (matrix == null) return list;
int row = matrix.length;
int col = matrix[0].length;
int[][] mark = new int[row][col];
int i = 0;
int j = 0;
setMark(0, 0, list, matrix, mark, 'r');
return list;
}
/**
* @param x 当前横坐标
* @param y 当前纵坐标
* @param list 接收链表
* @param array 原数组
* @param mark 标记数组
* @param direction 上一个坐标走到这个左边走的方向,u d l r 分别表示上、下、左、右
*/
private void setMark(int x, int y, ArrayList<Integer> list, int[][] array, int[][] mark, char direction) {
list.add(array[x][y]);
mark[x][y] = 1; // 这个位置已经走过了
switch (direction) {
case 'u': // ⬆ 上
if (mark[x - 1][y] == 0) { // 可以接着往上走
setMark(x - 1, y, list, array, mark, 'u');
} else if (mark[x][y + 1] == 0) { // 不能往上走,所以往右走
setMark(x, y + 1, list, array, mark, 'r');
} else { // 可能走的路全不能走
return;
}
break;
case 'd': // ⬇ 下
if (x == array.length - 1) { // 走到下边界了,直接往左走
if (y == 0) { // 即是下边界,也是左边界,无路可走
return;
} else {
setMark(x, y - 1, list, array, mark, 'l');
}
} else if (mark[x + 1][y] == 0) { // 没有走到边界,还可以接着往下走
setMark(x + 1, y, list, array, mark, 'd');
} else if (mark[x][y - 1] == 0) { // 即没到边界,又不能往下走,只能往左走
setMark(x, y - 1, list, array, mark, 'l');
} else { // 可能走的路全不能走
return;
}
break;
case 'l': // <-- 左
if (y == 0) { // 已经在左边界
if (mark[x - 1][y] == 0) { // 往上一步的位置没走过
setMark(x - 1, y, list, array, mark, 'u');
} else { // 往上一步的位置已走过,则现在无法走了
return;
}
} else if (mark[x][y - 1] == 0) { // 没有到边界,左边又没走过,可以往左走
setMark(x, y - 1, list, array, mark, 'l');
} else if (mark[x - 1][y] == 0) {
setMark(x - 1, y, list, array, mark, 'u');
} else { // 可能走的路全不能走
return;
}
break;
case 'r': // --> 右
if (y == array[0].length - 1) { // 到了右边界
if (x == array.length - 1) { // 也到了下边界
return; // 到了右边界,也到了下边界,无路可走
} else { // 到了右边界,没到下边界,往下走
setMark(x + 1, y, list, array, mark, 'd');
}
} else if (mark[x][y + 1] == 0) { // 没有到右边界,右边没走过,则继续往右走
setMark(x, y + 1, list, array, mark, 'r');
} else if (x == array.length - 1) { // 当前已经是下边界,无路可走
return;
} else if (mark[x + 1][y] == 0) { // 没有到右边界和下边界,右边又不能走,只能往下走
setMark(x + 1, y, list, array, mark, 'd');
} else {
return;
}
break;
}
}
}
时间复杂度:O(NM),遍历一次矩阵;
空间复杂度:O(NM),每递归一次就创建多一次虚拟机栈。
3.2 层遍历法
package pers.klb.jzoffer.hard;
import java.util.ArrayList;
/**
* @program: JzOffer2021
* @description: 顺时针打印矩阵(层遍历法)
* @author: Meumax
* @create: 2020-08-11 19:32
**/
public class ClockwisePrint {
public ArrayList<Integer> printMatrix(int[][] matrix) {
ArrayList<Integer> list = new ArrayList<>();
if (matrix == null) return list;
int lx = 0; // 行坐标
int ly = 0; // 列坐标
int rx = matrix.length - 1; // 右下角行坐标
int ry = matrix[0].length - 1; // 右下角列坐标
while (lx <= rx && ly <= ry) {
printCircle(lx++, ly++, rx--, ry--, matrix, list);
}
return list;
}
private void printCircle(int lx, int ly, int rx, int ry, int[][] matrix, ArrayList<Integer> list) {
for (int i = ly; i <= ry; i++) { // --> 右
list.add(matrix[lx][i]);
}
for (int j = lx + 1; j <= rx; j++) { // ⬇ 下
list.add(matrix[j][ry]);
}
if (rx - lx >= 1 && ry - ly >= 1) { // <-- 左
for (int k = ry - 1; k >= ly; k--) {
list.add(matrix[rx][k]);
}
}
if (ry - ly >= 1 && rx - lx - 1 >= 1) { // ⬆ 上
for (int m = rx - 1; m >= lx + 1; m--) {
list.add(matrix[m][ly]);
}
}
}
}
时间复杂度:O(mn), 矩阵中每个元素遍历一次
空间复杂度:O(mn), 每个元素需要存下来
4、解题心得
递归法是直接用直觉的方式进行编程,有时候可以跳出直觉,把顺时针遍历看成一层一层往里遍历,反而更加简单。