赞
踩
第五篇,继续。
力扣【59】:螺旋矩阵II
(Hint: 我认为blog记录得到最终代码的过程,所以有些代码片是逻辑错误(会标明“有错”),体现改进,所以阅读时请不要错位,或复制错误代码。)
给你一个正整数 n ,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
示例 1:
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
示例 2:
输入:n = 1
输出:[[1]]
提示:
1 <= n <= 20
我首先画n <= 5的顺时针过程,第一步发现:行列交错填充数组。所以:
定义一个bool=0(初始值);
if(bool=0),操作行,结束时bool=1;
else,操作列。在一个for循环内。
过程如下:
for(int k = 0; k < xxx;k++){ //xxx代表还没确定范围,需要根据n找规律。
if(!bool){
//操作行
}else
//操作列
}
行不通,原因,循环条件不统一:
1. 在操作行/列,for循环变量i一会向右,i++;一会向左,i--;
2. 另外for循环变量i的初始值每次也不一样,所以循环条件一直在动。
难道还要if判断什么时候++,什么时候--?那我要加的if好多,所以换思路。
第二步发现:
循环操作:向→;再↓;再←;再↑。4个为一组,看作一轮。所以有了大框架:
for(int k = 0;k < (2*n-1);k++){ //为什么k < (2*n-1),看下一点找规律 switch(k % 4){ case 0: //方向向右 //执行操作; break; case 1: //方向向下 //执行操作; break; case 2: //方向向左 //执行操作; break; case 3: //方向向下 //执行操作; break; } //xxxx 后面分析还要加什么。 }
n=2时,3步:向→;再↓;再←;
n=3时,5步:向→;再↓;再←;再↑;向→;
n=4时,7步:向→;再↓;再←;再↑;向→;再↓;再←;
n=5时,9步:向→;再↓;再←;再↑;向→;再↓;再←;再↑;再→;
所以for循环的范围是k < (2*n-1)。
接着填充每个方向内的代码,即case下的代码,完成矩阵元素填充:
以向右操作case0,作完整分析:(其余类似)
首先想到for循环:
int i=0;//行坐标
int j = 0;//列坐标
case 0:
for(;j <= line; j++) {//初始值——没有固定值,先不写;范围——line每次减1,初始值(n-1);向右,所以j++
matrix[i][j] = num++; //先赋值后++,所以num初始为1.
}
j -=1; //传给下一个case的j,for循环结束,j = n,所以要减1.
i +=1; //因为转角元素已经填充,所以↓,不能重复操作转角元素,所以i+1.
break;
但我想着写成while更好看,所以改变了下:
case 0: //方向向右
while(j <= line){
matrix[i][j] = num++; //先赋值后++
j++;
}
j -=1;
i +=1;
break;
其余同理完成case填充,每个while结束后调整i,j,都是为了递交下一棒时索引正确。最后大框架得到中间步骤1:
//每个case内的代码操作填充完毕 for(int k = 0;k < (2*n-1);k++){ switch(k % 4){ case 0: //方向向右 while(j <= line){ matrix[i][j] = num++; //先赋值后++ j++; } j -=1; i +=1; break; case 1: //方向向下 while(i <= line){ matrix[i][j] = num++; i++; } i -=1; //传给case2,因为while结束,i = n,超出范围,所以减1 j -=1; // 转角元素已经操作过。 break; case 2: //方向向左 while(j >= row){ matrix[i][j] = num++; j--; } j +=1; //while结束后,j=-1,超出下标范围 i -=1; //转角元素已经操作过 break; case 3: //方向向下 while(i >= row+1){ matrix[i][j] = num++; i--; } i +=1; j +=1; break; default: break; }
while控制范围的条件:line和row变量。
所以大框架得到中间步骤2:
//完善for循环中:line和row变量操作—— if(k%4 == 3) int line = n - 1; //初始值 int row = 0; //初始值 int num = 1; //填入数字,初始1,不是0 for(int k = 0;k < (2*n-1);k++){ switch(k % 4){ case 0: //方向向右 while(j <= line){ matrix[i][j] = num++; //先赋值后++ j++; } j -=1; i +=1; break; case 1: //方向向下 while(i <= line){ matrix[i][j] = num++; i++; } i -=1; j -=1; break; case 2: //方向向左 while(j >= row){ matrix[i][j] = num++; j--; } j +=1; i -=1; break; case 3: //方向向下 while(i >= row+1){ matrix[i][j] = num++; i--; } i +=1; j +=1; break; default: break; } 一步 if(k%4 == 3){ //此时要开启新一轮, 下一步是→。收缩到内圈顺时针旋转。 line--; row++; } }
总结:再加上初始化vector二维数组的定义,得到完整代码:
//通过测试,没有问题。 class Solution { public: vector<vector<int>> generateMatrix(int n) { vector<vector<int>> matrix(n,vector<int> (n,0)); //返回值:得到的矩阵 //for(int i = 0;i < n;i++){ //怎么初始化容器的二维数组? // vector<int> subrow = new int[n]]; //初始化指针,分配内存。 // matrix.push_back(subrow); //}这个是错误方式初始化。 int i = 0;//行指针 int j = 0;//列指针,操作matrix坐标。 //控制每轮的范围 int line = n - 1; int row = 0; int num = 1;//填入数字,初始1,不是0 for(int k = 0;k < (2*n-1);k++){ switch(k % 4){ case 0: //方向向右 while(j <= line){ matrix[i][j] = num++; //先赋值后++ j++; } j -=1; i +=1; break; case 1: //方向向下 while(i <= line){ matrix[i][j] = num++; i++; } i -=1; j -=1; break; case 2: //方向向左 while(j >= row){ matrix[i][j] = num++; j--; } j +=1; i -=1; break; case 3: //方向向下 while(i >= row+1){ matrix[i][j] = num++; i--; } i +=1; j +=1; break; default: break; } if(k%4 == 3){ line--; row++; } } return matrix; } };
额外补充vector初始化:
1 初始化一维数组:
vector<int> nums();或vector<int> nums;//创建空容器,没有元素。可以看成一维数组。
vector<int> nums(n,0);//创建有n个元素的容器,每个元素的值,初始化为0.
2 初始化二维数组:
vector<int> matrix(n,vector<int> (m,0));//n行m列,元素初始化为0.
循环不变量:
(1)理解:遇到循环,始终坚持一种区间处理原则,[ )或[ ]或( ],无论哪种类型都可以,但一定要从头坚持到尾。不可以混合多个区间处理原则,一会一变,就会陷入逻辑混乱。
(2)在【记录一:二分查找】中,同理。
思路对比:
我的思路:虽然发现了循环规律,但是我把每一步单独拆开操作,for循环一次代表下一步:遇到转角元素,要改变方向。所以相对复杂;
卡哥思路:把4步看作一个整体,while循环一次代表完成一圈。更简单。
代码实现:
//靠自己按新思路写一遍,坚持左闭右开区间,即转角元素交给下一条边填充。通过测试。 class Solution { public: vector<vector<int>> generateMatrix(int n) { vector<vector<int>> matrix(n,vector<int> (n,0));//返回值 int i = 0; //行坐标 int j = 0; //列坐标 int offset = 0;//每次收缩内圈 int num = 1; int k = 0; while(k < n/2){ for(;j < n - 1-offset;j++){ //没有操作转角元素,留给下一个for matrix[i][j] = num++; } for(;i < n-1-offset;i++){ matrix[i][j] = num++; } for(;j > offset;j--){ matrix[i][j] = num++; } for(;i > offset;i--){ matrix[i][j] = num++; } //收缩内圈 i++; j++; offset++; k++; } //n为奇数 if(n%2){ matrix[i][j] = num++; } return matrix; } };
对比参考代码:
1 直接用i ,j 操作元素,相当于startx和starty,所以可以不要startx和starty。
2 我用k控制循环次数,多了两行代码:int k = 0;k++;
但参考直接loop--即可。(采纳)
另外我想再写一个区间是左开右闭,即转角元素交给当前边填充:(也就是第一次尝试的思路)
注意:第四个for没有“=”,而且第一个for开始需要特别处理起始元素。所以比左闭右开麻烦一些,需要注意边界。
//通过测试。左开右闭,即转角元素交给当前边填充。特别处理起始元素。 class Solution { public: vector<vector<int>> generateMatrix(int n) { vector<vector<int>> matrix(n,vector<int> (n,0)); int i = 0;//横坐标 int j =0;//纵坐标 int offset = 0;//收缩内圈 int loop = n/2;//循环圈数 int mid = n/2; //正中间的元素 int num = 1;//填充元素 while(loop--){ matrix[i][j] = num++; //起始元素处理 for(j = j+1;j <= n-1-offset;j++){ matrix[i][j] = num++; } for(i = i+1,j=j-1;i <= n-1-offset;i++){ matrix[i][j] = num++; } for(j=j-1,i=i-1; j >= offset;j--){ matrix[i][j] = num++; } for(i=i-1,j=j+1;i > offset;i--){ //注意没有“=” matrix[i][j] = num++; } i++; j++; offset++; } if(n%2){ matrix[mid][mid] = num++; } return matrix; } };
(1)我发现了规律,想到了循环,但是没有看作一个整体,而是一步一步的划分开。所以操作麻烦,每次都要修正i,j坐标。但是按照参考思路,每个while完成一圈就很方便。
通过控制i,j,始终坚持循环不变量,遵循从一而终的区间原则,通过本文可以掌握较好。
(欢迎指正,转载标明出处)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。