一、做题感受
今天是训练营打卡的第二天,题目相比第一天难度增加了许多,尤其是在螺旋矩阵那里看题解才做出来。然后分别对这几道题说一下心得吧。
977.有序数组的平方:暴力做法的话比较简单,就一个排序的应用;然后就是,这道题还可以用双指针,这是我一开始没想到的,后边发现其本身就是排序好了的,只是存在正负导致平方值排序不同,突然就明朗了。
209.长度最小的子数组:这个的话,就是一个经典的滑动窗口的应用了,由于之前就做过类似的,感觉也是经典的套模板,找到左边界和右边界确定长度再不断比较得出最小的既可。
59.螺旋矩阵II:这个题算是二刷了,的确有难度,再次卡住了,但今天看题解发现就是一个按照圈数不断遍历的过程,总算是真的理解了。
二、题目及题解
1、977. 有序数组的平方 - 力扣(LeetCode)
首先展示一下我写的暴力解法:排序可以用自带的sort()函数,由于原数组元素本身是排好序的,sort()应放在平方后的数组之后。
附上截图:
再看看双指针的做法:
在这里要注意:在定义vector数组时,由于后边需要用[]来访问,就意味着是随机访问了,这必须初始化声明的时候手动分配内存初始化。(这一点很重要);如果单纯声明不初始化大小,可以用push_back这些一个个来压入,但不能用[]来随机访问。
因为数组本身是排好序了的,相当于正数的平方本身就排好序了,在这里就能直接使用双指针将负数的平方与后边的数比较再相应移动既可。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> ans(nums.size(),0); //copy一个与nums同样长度的数组ans,初始化值全为0
int left=0;
int right=nums.size()-1;
int n=nums.size()-1;
while(left<=right)
{
if(nums[left]*nums[left]<nums[right]*nums[right]){
ans[n--]=nums[right]*nums[right];
right--;
}
else{
ans[n--]=nums[left]*nums[left];
left++;
}
}
return ans;
}
};
2、209. 长度最小的子数组 - 力扣(LeetCode)
滑动窗口的应用:
先初始化左边界和右边界,再定义并初始化result来存储最小长度(这里由于是求得最小值,故初始化的result需要取一个可能出现的最大值),用sum来存储临时子数组的和与target比较从而移动左右边界,通过while不断遍历比较得出最小长度(每次满足目标值的子数组长度用right-left+1表示)。
看文字可能不好理解,这里直接看代码:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int left = 0; //初始化左边界为0
int right = 0; //初始化右边界为0
int sum = 0;
int result = nums.size()+1; //初始化result为一个大于数组长度的值
while(right<nums.size())
{
while(right<nums.size() && sum<target) //此处需要再次加上right<nums.size(),防止right++超出size运行出错
{
sum+=nums[right];
right++;
}
while(sum>=target)
{
sum-=nums[left];
left++;
}
result = min(result,right-left+1); //取每次符合条件区间长度的最小值
}
return result==nums.size()+1?0:result;
}
};
//滑动窗口
3、59. 螺旋矩阵 II - 力扣(LeetCode)
不断模拟,找出圈数与n的关系,用num标记每个位置的值,每经过一个位置就+1,循环遍历每一圈(上,右,下,左顺序),依次填值。
这里直接看代码(载自某位大佬题解),更好理解:
注意看我写的注释,应该就挺好理解。(有个很方便理解的点:内层循环都是一样的for循环条件,都是j++,j在改变,那么遍历的时候不变的一定是i相关表示,变化的一定是j相关表示)
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n,0)); //定义并初始化二维数组(初始化元素全为0)
int num = 1; //从1开始
for(int i=0;i<n/2;i++) //共有n/2个圈(即进行n/2次以下循环),每个圈按照顺序:上,右,下,左
{
for(int j=i;j<n-i-1;j++) //上:行不变,列增加
res[i][j] = num++;
for(int j=i;j<n-i-1;j++) //右:列不变,行增加
res[j][n-i-1] = num++;
for(int j=i;j<n-i-1;j++) //下:行不变,列减小
res[n-i-1][n-j-1] = num++;
for(int j=i;j<n-i-1;j++) //左:列不变,行减小
res[n-j-1][i] = num++;
}
if(n%2==1) //n为奇数时,中间的数未能记录在内,直接取最大值(最后的num)
res[n/2][n/2] = num;
return res;
}
};
再附上一张代码随想录写了注释的另一写法(思路大体一样,可以看一下注释,方便理解):
class Solution {
public: //思路的重点在于loop的设置(将螺旋矩阵看成多个大圈包小圈),以及offset(圈的边长=n-offset)的设置
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组res[n][n]:0--n-1
int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
int loop = n / 2; // 圈数(每个圈循环几次),例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
int count = 1; // 用来给矩阵中每一个位置赋值,从1开始不断增加
int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位
int i,j;
while (loop --)
{
i = startx; //i,j每一圈重新赋值(按照每一圈的起始点),且i,j随着循环是一直在变的
j = starty;
// 下面开始的四个for就是模拟转了一圈
// 模拟填充上行从左到右(左闭右开,最右边一个没有取到)
for (j; j < n - offset; j++) {
res[i][j] = count++;
}
// 模拟填充右列从上到下(左闭右开,最下边一个没有取到)
for (i; i < n - offset; i++) {
res[i][j] = count++;
}
// 模拟填充下行从右到左(左闭右开)
for (; j > starty; j--) {
res[i][j] = count++;
}
// 模拟填充左列从下到上(左闭右开)
for (; i > startx; i--) {
res[i][j] = count++;
}
// 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
startx++;
starty++;
// offset 控制每一圈里每一条边遍历的长度
offset += 1;
}
// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
if (n % 2)
res[mid][mid] = count;
return res;
}
};
三、小结
今天的打卡任务到此也就结束了,花了时间刷题,感觉也挺有收获。写的这篇内容可能解释的不够清晰但也算是我今天刷题的心得,也是希望自己每日打卡能坚持下去,把自己想到的都写下来了。我是算法小白,但也希望终有所获。