拿下螺旋矩阵!LeetCode 59. 螺旋矩阵II,一招学会循环不变量
题目本身不涉及高深算法,却极其考验对代码的掌控能力。本文将带你用“循环不变量”原则,轻松攻克这道题。
题目描述
给你一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵。
示例:
text
输入:n = 3
输出:[
[1, 2, 3],
[8, 9, 4],
[7, 6, 5]
]
解题思路
这道题就是典型的模拟过程题目。我们需要按照顺时针方向,一圈一圈地给矩阵填充数字:
- 填充上行:从左到右
- 填充右列:从上到下
- 填充下行:从右到左
- 填充左列:从下到上
然而,边界条件非常多。很多同学写的时候一会儿左闭右开,一会儿左闭右闭,最后代码越写越乱,一进循环深似海。
核心方法论:循环不变量
要写好这道题,必须坚持循环不变量原则。通俗说,就是在每一圈的每一条边上,都采用相同的区间规则。
本文采用左闭右开原则。即每条边只处理起点,不处理终点,拐角处的元素交给下一条边来处理。
一圈的拆解(左闭右开)
假设我们画边长为4的正方形,一圈的遍历如下:
- 上行:从左到右,遍历
[start, n-offset) - 右列:从上到下,遍历
[start, n-offset) - 下行:从右到左,遍历
(start, n-offset]逆序,但左闭右开写法其实也是j > startY作为边界 - 左列:从下到上,遍历
i > startX
这样做的好处是:每条边的处理规则完全一致,不会出现边界冲突。
代码详解(JavaScript版)
javascript
var generateMatrix = function(n) {
// 初始化二维数组
let res = new Array(n).fill().map(() => new Array(n).fill(0));
let startX = 0, startY = 0; // 每圈起始坐标
let loop = Math.floor(n / 2); // 需要转的圈数
let mid = Math.floor(n / 2); // 中心点坐标(n为奇数时)
let count = 1; // 填充数字
let offset = 1; // 控制边长度
let i, j;
while (loop--) {
i = startX;
j = startY;
// 1. 上行:从左到右
for (j = startY; j < n - offset; j++) {
res[i][j] = count++;
}
// 2. 右列:从上到下
for (i = startX; i < n - offset; i++) {
res[i][j] = count++;
}
// 3. 下行:从右到左
for (; j > startY; j--) {
res[i][j] = count++;
}
// 4. 左列:从下到上
for (; i > startX; i--) {
res[i][j] = count++;
}
// 下一圈起始位置向内缩一格
startX++;
startY++;
// 边长缩减
offset += 1;
}
// n为奇数时,中心点单独赋值
if (n % 2 === 1) {
res[mid][mid] = count;
}
return res;
};
测试用例
javascript
console.log(generateMatrix(3));
// 输出: [[1,2,3],[8,9,4],[7,6,5]]
console.log(generateMatrix(4));
// 输出: 4x4 螺旋矩阵
复杂度分析
- 时间复杂度:O(n²),需要遍历矩阵的每一个元素。
- 空间复杂度:O(1),除了返回结果数组外,只使用了常数个额外变量。
总结
通过这道题,我们要记住:
- 模拟类题目一定要先规划好边界规则。
- 循环不变量 是写出正确边界控制的关键。
- 坚持左闭右开能让代码逻辑清晰,避免混乱。
推荐搭配视频学习:《代码随想录》算法视频公开课(拿下螺旋矩阵)
掌握了这个方法,无论 n 多大,我们都能有条不紊地画出螺旋矩阵。快去试试吧!