54. Spiral Matrix
Given an m x n matrix, return all elements of the matrix in spiral order.
Example 1:

Example 2:

Solution:
总体思路
根据题意,我们从左上角 (0,0) 出发,按照「右下左上」的顺序前进:
- 首先向右走,如果到达矩阵边界,则向右转 90, 前进方向变为向下。
- 然后向下走,如果到达矩阵边界,则向右转 90, 前进方向变为向左。
- 然后向左走,如果到达矩阵边界,则向右转 90, 前进方向变为向上。
- 然后向上走,先从 7 走到 4,然后从 4 准备向上走,但上面的 1 是一个已经访问过的数字,那么向右转 90, 前进方向变为向右。
- 重复上述过程,直到答案的长度为 mn。
方法一:标记
- 对于已经访问过的数字,可将其标记为 ∞ 或者空,从而避免重复访问。
- 用一个长为 4 的方向数组 \(DIRS=[(0,1),(1,0),(0,−1),(−1,0)]\) 分别表示右下左上 4 个方向。同时用一个下标 \(di\) 表示当前方向,初始值为 0,表示一开始向右。
- 每次移动,相当于把行号增加 \(DIRS[di][0]\),把列号增加 \(DIRS[di][1]\)。
- 向右转 90,相当于把 \(di\) 增加 1,但在 \(di=3\) 时要回到 \(di=0\)。两种情况合二为一,把 \(di\) 更新为 \((di+1) \mod 4\)。
class Solution {
private static final int[][] DIRS = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 右下左上
public List<Integer> spiralOrder(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
List<Integer> ans = new ArrayList<>(m * n); // 预分配空间
int i = 0;
int j = 0;
int di = 0;
for (int k = 0; k < m * n; k++) { // 一共走 mn 步
ans.add(matrix[i][j]);
matrix[i][j] = Integer.MAX_VALUE; // 标记,表示已经访问过(已经加入答案)
int x = i + DIRS[di][0];
int y = j + DIRS[di][1]; // 下一步的位置
// 如果 (x, y) 出界或者已经访问过
if (x < 0 || x >= m || y < 0 || y >= n || matrix[x][y] == Integer.MAX_VALUE) {
di = (di + 1) % 4; // 右转 90°
}
i += DIRS[di][0];
j += DIRS[di][1]; // 走一步
}
return ans;
}
}
// 时间复杂度:O(mn),其中 m 和 n 分别为 matrix 的行数和列数。
// 空间复杂度:O(1)。返回值不计入。
方法二:不标记
上面的做法需要修改 matrix,能否不修改呢?
示例 2 这 12 个数字,可以分为以下 5 组:
-
1→2→3→4
-
8→12
-
11→10→9
-
5
-
6→7
其中第 1,3,5 组都是向右或者向左走的,长度依次为 4,3,2,这是一个从 n=4 开始的逐渐递减的序列。
其中第 2,4 组都是向下或者向上走的,长度依次为 2,1,这是一个从 m−1=2 开始的逐渐递减的序列。
由于走的步数是有规律的,我们可以精确地控制在每个方向上要走多少步,无需判断是否出界、是否重复访问:
- 从 (0,−1) 开始。
- 一开始,向右走 n 步,每次先走一步,再把数字加入答案。走 n 步即 1→2→3→4,矩阵第一排的数都加入了答案。
- 然后向下走 m−1 步,即 8→12。
- 然后向左走 n−1 步,即 11→10→9。
- 然后向上走 m−2 步,即 5。
- 然后向右走 n−2 步,即 6→7。
- 重复上述过程,直到答案的长度等于 mn。
代码实现时,可以这样简化代码:
- 一开始走 n 步。
- 把 n,m 分别更新为 m−1,n,这样下一轮循环又可以走 n 步(相当于走了 m−1 步),无需修改其他逻辑。
- 把 n,m 分别更新为 m−1,n,这样下一轮循环又可以走 n 步(相当于走了 n−1 步)。
- 把 n,m 分别更新为 m−1,n,这样下一轮循环又可以走 n 步(相当于走了 m−2 步)。
- 依此类推,每次只需把 n,m 分别更新为 m−1,n 即可。
答疑 问:如何说明这个规律的正确性?
答:在上文的例子中,我们把示例 2 分成了 5 组。一般地,每 4 组,我们会把矩阵最外面一圈去掉(就像剥洋葱),矩阵的行数会减少 2,列数会减少 2。所以行列的减少是有规律的。
思考题
假如 \(m \leq 10^9, n \leq 10^9\).
输出走了 \(k\) 步之后,你所在的格子的坐标(行列编号)。
你能用 \(O(log)\) 或者 \(O(1)\) 时间解决吗?
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> result = new ArrayList<Integer>();
int row = matrix.length; // 3
int col = matrix[0].length; // [1,2,3,4] 4
// base case
if (row == 0 || col == 0){
return result;
}
if (row == 1){
// [[1,2,3,4]]
for (int i = 0; i < col; i++){
result.add(matrix[row-1][i]);
}
return result;
}
if (col == 1){
// [[1] [2] [3]]
for (int i = 0; i < row; i++){
result.add(matrix[i][col-1]);
}
return result;
}
// index
int left = 0;
int right = col - 1;
int up = 0;
int down = row - 1;
while(left < right && up < down){
// left -> right
for (int i = left; i < right; i++){
result.add(matrix[up][i]);
}
// up -> down
for (int j = up; j < down; j++){
result.add(matrix[j][right]);
}
// right->left
for (int i = right; i > left; i--){
result.add(matrix[down][i]);
}
// down -> up
for (int j = down; j > up; j--){
result.add(matrix[j][left]);
}
left++;
right--;
up++;
down--;
}
// if there is nothing left
if (left > right || up > down){
return result;
}
// if there is left
// one col or one row
if (left == right){
// col
for (int i = up; i <= down; i++){
result.add(matrix[i][left]);
}
}else{
// row
for (int i = left; i <= right; i++){
result.add(matrix[up][i]);
}
}
return result;
}
}
// TC: O(n)
// SC: O(1)
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> result = new ArrayList<Integer>();
int row = matrix.length;
int col = matrix[0].length;
int left = 0;
int right = col - 1;
int up = 0;
int down = row - 1;
while(left < right && up < down){
for (int i = left; i < right; i++){
result.add(matrix[up][i]);
}
for (int j = up; j < down; j++){
result.add(matrix[j][right]);
}
for (int i = right; i > left; i--){
result.add(matrix[down][i]);
}
for (int j = down; j > up; j--){
result.add(matrix[j][left]);
}
left++;
right--;
up++;
down--;
}
if (left > right || up > down){
return result;
}
if (left == right){
for (int i = up; i <= down; i++){
result.add(matrix[i][left]);
}
}else {
for (int i = left; i <= right; i++){
result.add(matrix[up][i]);
}
}
return result;
}
}
// TC: O(n)
// SC: O(1)