54. Spiral Matrix(螺旋矩阵)
1. 题目描述
给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。
示例 1:
输入:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
输出: [1,2,3,6,9,8,7,4,5]
示例 2:
输入:
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9,10,11,12]
]
输出: [1,2,3,4,8,12,11,10,9,5,6,7]
2. 按层遍历(Layer-by-Layer)
2.1 解题思路
这题要求的遍历顺序为:左上 -> 右上 -> 右下 -> 左下 -> 左上,然后内层重复上述遍历顺序,所以我们采取按层遍历的方法,如下图所示:
红色线为第一层遍历,蓝线为第二层遍历,橙线为当前遍历层数起点;
根据上图,我们可以总结出一般遍历步骤:
- 计算有多少层需要遍历,和单层需要遍历的个数(防止重复遍历);
- 判断是否是竖向单层情况:列数为1;如果是,只需向下遍历即可;如果不是,则需要按照左上 -> 右上 -> 右下 -> 左下 -> 左上进行遍历,如果有一边没有,则停止遍历;
- 重新计算右边界,后开始下一层遍历;
首先我们来观察一共有多少层需要遍历,我们发现遍历层数 = (min(当层列数[jBoundary], 当层行数[iBoundary]) - 1 ) / 2,比如jBoundary - j == 1这个例子,遍历层数 = (min(3, 4) - 1) / 2 = 1;如果是竖向单层遍历,非常简单,只需要限制i小于行数边界即可;至于其他情况,我们观察到每行每列的起始和结束序号为:[k(每层起点序号), iBoundary - 1 或 jBoudary - 1]。但是仅仅限制序号边界是不够的,比如下面例子的内层循环时,在只有序号边界限制的情况下,会重复访问[1,1],[2,2]这几个点:
[
[1,2,3,4],
[5,6,7,8],
[9,10,11,12]
]
所以我们增加遍历剩余个数(alls)来进行限制,每访问一个点,剩余个数减一,当alls = 0时,则不需要遍历任何数字。
那alls怎么计算得来的呢?有两种情况需要考虑:1)横向单层(即行数 = 1);2)非横向单层。对于第一种情况,alls = jBoundary - j;对于第二种情况,我们可以先计算当前层构成的周长:行数 * 2 + 行数 * 2,但是这种计算方法会重复计算四个角,所以最后需要减去4:行数 * 2 + 行数 * 2 - 4。
2.2 实例代码
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
int iBoundary = matrix.size();
vector<int> ans;
if (!iBoundary) return ans;
int jBoundary = matrix[0].size(), layers = (min(iBoundary, jBoundary) - 1) / 2;
for (int k = 0; k <= layers; k++) {
int i = k, j = k,
alls = iBoundary - i == 1 ? jBoundary - j : 2 * (iBoundary - i) + 2 * (jBoundary - j) - 4; // 每层包含的数字总数
if (jBoundary - j != 1) { // 左上 -> 右上 -> 右下 -> 左下 -> 左上顺序遍历
while (j < jBoundary) { ans.push_back(matrix[i][j]); j++; alls--; }
j--; i++;
while (alls > 0 && i < iBoundary) { ans.push_back(matrix[i][j]); i++; alls--; }
i--; j--;
while (alls > 0 && j >= k) { ans.push_back(matrix[i][j]); j--; alls--; }
j++; i--;
while (alls > 0 && i > k) { ans.push_back(matrix[i][j]); i--; }
}
else { while (i < iBoundary) { ans.push_back(matrix[i][j]); i++; }} // 竖向单层遍历
iBoundary--; jBoundary--; // 右边界收缩
}
return ans;
}
};