版本说明
当前版本号[20230810]。
版本 | 修改说明 |
---|---|
20230810 | 初版 |
目录
文章目录
- 版本说明
- 目录
- 59.螺旋矩阵II
- 思路
- 左闭右开方法
- 左闭右闭方法
- 两种方法的区别
- 总结
59.螺旋矩阵II
力扣题目链接
更多内容可点击此处跳转到代码随想录,看原版文件
给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
示例:
输入: 3 输出: [ [ 1, 2, 3 ], [ 8, 9, 4 ], [ 7, 6, 5 ] ]
思路
这道题目可以说在面试中出现频率较高的题目,本题并不涉及到什么算法,就是模拟过程,但却十分考察对代码的掌控能力。
要如何画出这个螺旋排列的正方形矩阵呢?
相信很多同学刚开始做这种题目的时候,上来就是一波判断猛如虎。
结果运行的时候各种问题,然后开始各种修修补补,最后发现改了这里那里有问题,改了那里这里又跑不起来了。
大家还记得我们讲解的二分法,提到如果要写出正确的二分法一定要坚持循环不变量原则。
而求解本题依然是要坚持循环不变量原则。
模拟顺时针画矩阵的过程:
- 填充上行从左到右
- 填充右列从上到下
- 填充下行从右到左
- 填充左列从下到上
由外向内一圈一圈这么画下去。就像下图一样:
可以发现这里的边界条件非常多,在一个循环中,如此多的边界条件,如果不按照固定规则来遍历,那就是一进循环深似海,从此offer是路人。
这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。
左闭右开方法
那么我按照左闭右开的原则,来画一圈,大家看一下:
这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。
这也是坚持了每条边左闭右开的原则。
一些同学做这道题目之所以一直写不好,代码越写越乱。
就是因为在画每一条边的时候,一会左开右闭,一会左闭右闭,一会又来左闭右开,岂能不乱。
代码如下,已经详细注释了每一步的目的,可以看出while循环里判断的情况是很多的,代码里处理的原则也是统一的左闭右开。
通过模拟四个方向的填充过程,将数字从1开始按顺时针螺旋的方式填入矩阵中。
整体Java代码如下:
class Solution {
public int[][] generateMatrix(int n) {
int loop = 0; // 控制循环次数
int[][] res = new int[n][n];
int start = 0; // 每次循环的开始点 (start, start)
int count = 1; // 定义填充数字
int i, j;
while (loop++ < n / 2) { // 判断边界后,loop从1开始
// 模拟上侧从左到右
for (j = start; j < n - loop; j++) {
res[start][j] = count++; // 在上侧从左到右的行中填充数字
}
// 模拟右侧从上到下
for (i = start; i < n - loop; i++) {
res[i][j] = count++; // 在右侧从上到下的列中填充数字
}
// 模拟下侧从右到左
for (; j >= loop; j--) {
res[i][j] = count++; // 在下侧从右到左的行中填充数字
}
// 模拟左侧从下到上
for (; i >= loop; i--) {
res[i][j] = count++; // 在左侧从下到上的列中填充数字
}
start++;
}
if (n % 2 == 1) {
res[start][start] = count; // 处理矩阵边长为奇数的情况,中心点单独填充数字
}
return res;
}
}
- 时间复杂度 O(n^2): 模拟遍历二维矩阵的时间
- 空间复杂度 O(1)
左闭右闭方法
这段代码使用左闭右闭的方式来定义边界。我们使用四个变量 left
、right
、top
、bottom
来表示矩阵的边界,初始时 left=0
,right=n-1
,top=0
,bottom=n-1
。
- 在循环中,我们首先从左到右填充上侧的行,行号为
top
,列号从left
到right
(包括left
和right
),逐个填充数字count
,并将count
加 1。然后,我们将top
的值加 1。 - 接着,从上到下填充右侧的列,列号为
right
,行号从top
到bottom
(包括top
和bottom
),逐个填充数字count
,并将count
加 1。然后,我们将right
的值减 1。 - 然后,从右到左填充下侧的行,行号为
bottom
,列号从right
到left
(包括right
和left
),逐个填充数字count
,并将count
加 1。然后,我们将bottom
的值减 1。 - 最后,从下到上填充左侧的列,列号为
left
,行号从bottom
到top
(包括bottom
和top
),逐个填充数字count
,并将count
加 1。然后,我们将left
的值加 1。
重复上述步骤,直到 count
的值达到 n*n。循环结束后,我们生成了按顺时针顺序螺旋排列的正方形矩阵。
最后,我们返回生成的结果矩阵 res
。
class Solution {
public int[][] generateMatrix(int n) {
int[][] res = new int[n][n];
int count = 1; // 定义填充数字
int left = 0, right = n - 1, top = 0, bottom = n - 1; // 定义边界
while (count <= n * n) {
// 从左到右填充上侧的行
for (int i = left; i <= right; i++) {
res[top][i] = count++;
}
top++;
// 从上到下填充右侧的列
for (int i = top; i <= bottom; i++) {
res[i][right] = count++;
}
right--;
// 从右到左填充下侧的行
for (int i = right; i >= left; i--) {
res[bottom][i] = count++;
}
bottom--;
// 从下到上填充左侧的列
for (int i = bottom; i >= top; i--) {
res[i][left] = count++;
}
left++;
}
return res;
}
}
两种方法的区别
左闭右开方法和左闭右闭方法的区别在于边界的定义方式。
- 左闭右开方法:边界的定义是左边界是闭区间,右边界是开区间。即在填充每一行或每一列时,右边界的索引不包含在当前填充的范围内。在填充完一行或一列后,边界会向内缩小。
- 左闭右闭方法:边界的定义是左边界和右边界都是闭区间,即在填充每一行或每一列时,左右边界的索引都包含在当前填充的范围内。填充完一行或一列后,边界保持不变。
总结
首先,定义变量 loop
表示当前的螺旋圈数,初始值为 0。螺旋圈数表示当前正在填充的圈数,总共会填充 n/2 圈。
然后,创建一个大小为 n×n 的空矩阵 res
,用来存储结果。
开始循环,每次循环填充一圈。循环的条件是 loop++ < n / 2
,保证循环次数不超过 n/2。
- 在循环中,首先是从左到右填充上侧的行。使用变量
j
作为列的索引,从start
开始,一直到n - loop - 1
(因为最后一列由右侧从上到下填充),逐个填充数字count
,并将count
加 1。 - 接着,从上到下填充右侧的列。使用变量
i
作为行的索引,从start
开始,一直到n - loop - 1
(因为最后一行由下侧从右到左填充),逐个填充数字count
,并将count
加 1。 - 然后,从右到左填充下侧的行。这次填充的行号保持为
i
,而列号从j
开始递减,直到loop
。同样,逐个填充数字count
,并将count
加 1。 - 最后,从下到上填充左侧的列。这次填充的列号保持为
j
,而行号从i
开始递减,直到loop + 1
。逐个填充数字count
,并将count
加 1。
每填充完一圈,我们将变量 start
加 1,表示下一圈的起始位置。重复上述步骤,直到循环结束。
如果 n 是奇数,最后还需要填充矩阵的中心点,即 res[start][start]
,此时的 count 不需要加 1。
最后,返回生成的结果矩阵 res
。
整个过程按照数学上定义的顺时针顺序,从外圈向内圈逐步填充数字,最终生成了按顺时针顺序螺旋排列的正方形矩阵。具体也可以通过下图进行一个理解。
测试代码如下:
package shuzhu;
public class Day05 {
public int[][] generateMatrix(int n) {
int loop = 0; // 控制循环次数
int[][] res = new int[n][n]; // 结果矩阵
int start = 0; // 每次循环的开始点 (start, start)
int count = 1; // 定义填充数字
int i, j;
while (loop++ < n / 2) { // 循环次数不超过 n/2
// 模拟上侧从左到右
for (j = start; j < n - loop; j++) {
res[start][j] = count++; // 在上侧从左到右的行中填充数字
}
// 模拟右侧从上到下
for (i = start; i < n - loop; i++) {
res[i][j] = count++; // 在右侧从上到下的列中填充数字
}
// 模拟下侧从右到左
for (; j >= loop; j--) {
res[i][j] = count++; // 在下侧从右到左的行中填充数字
}
// 模拟左侧从下到上
for (; i >= loop; i--) {
res[i][j] = count++; // 在左侧从下到上的列中填充数字
}
start++; // 每次循环结束后,更新开始点的位置
}
if (n % 2 == 1) {
res[start][start] = count; // 处理矩阵边长为奇数的情况,中心点单独填充数字
}
return res;
}
public static void main(String[] args) {
Day05 solution = new Day05();
// 测试生成 3x3 的螺旋矩阵
int[][] matrix1 = solution.generateMatrix(3);
System.out.println("生成的 3x3 螺旋矩阵:");
for (int i = 0; i < matrix1.length; i++) {
for (int j = 0; j < matrix1[0].length; j++) {
System.out.print(matrix1[i][j] + " ");
}
System.out.println();
}
System.out.println();
}
}