代码随想录算法训练营第二天 |977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II ,数组总结
有序数组的平方
题目链接:https://leetcode.cn/problems/squares-of-a-sorted-array/
文章讲解:https://programmercarl.com/0977.%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E5%B9%B3%E6%96%B9.html
视频讲解: https://www.bilibili.com/video/BV1QB4y1D7ep
思路
-
暴力解法
把每一个元素平方,然后排序
-
双指针
平方之后原来的有序数列改变顺序的原因就是负数平方之后可能大于原来的整数,所以只需比较负数平方后是否大于后面的正数,然后插入相应位置即可
具体实现流程:
初始:
- 两个指针left,right分别指向数组的首尾
- 定义一个同样大小的新数组作为返回结果,并定义下标ans,指向末尾
移动指针,循环比较:
- 右指针平方 > 左指针平方时,right 所指的数的平方放入新数组ans下标处,右指针左移,直至找到一个正数平方小于当前left所指的负数平方(此时相当于找到了要将负数平方插入正数群体中的位置)
- 左指针平方 > 右指针平方时,left 所指的数的平方放入新数组ans下标处,左指针右移,进行下一个负数与正数的比较
结束条件:
- 通常在双指针法中,特别是在从左右两端向中间夹逼的场景,循环的终止条件一般都是是
left == right
。因为当left
和right
指针相遇时,意味着已经完成了所有需要的填充或操作,没有更多的元素需要处理,本题也不例外。
代码
vector<int> sortedSquares(vector<int> &nums)
{
//暴力解法--------------------------------
for (int i = 0; i < nums.size(); i++)
{
nums[i] = nums[i] * nums[i];
}
sort(nums.begin(), nums.end());
return nums;
//双指针实现-------------------------------
// 定义左指针和右指针,初始化为数组的首尾元素
int left = 0;
int right = nums.size() - 1;
// 定义结果数组,长度为nums.size(),初始化为0
int ans = nums.size() - 1 ;
vector<int> res(ans + 1, 0);
// 当左指针小于等于右指针时,进行循环
while(left <= right)
{
// 如果左指针指向的元素平方大于右指针指向的元素平方
if(nums[left] * nums[left] > nums[right] * nums[right])
{
// 将左指针指向的元素平方添加到结果数组的对应位置,左指针向右移动
res[ans--] = nums[left] * nums[left];
left++;
}
// 如果右指针指向的元素平方大于左指针指向的元素平方
else
{
// 将右指针指向的元素平方添加到结果数组的对应位置,右指针向左移动
res[ans--] = nums[right] * nums[right];
right--;
}
}
// 返回结果数组
return res;
}
长度最小的数组
我一开始还以为是这个子数组不需要是连续的,和数学中的数列的子列一样【笑哭】,然后我就直接排序来做了,真是无语了。
题目链接:https://leetcode.cn/problems/minimum-size-subarray-sum/
视频讲解:https://www.bilibili.com/video/BV1tZ4y1q7XE
思路
-
暴力解法
两个for循环不断寻找符合条件的子数组
-
滑动窗口法
核心:不断调整窗口(子序列)的起始与结束位置,直至达到目标
其实就是用for循环做了两个for循环做的事,但是省去了很多无用操作,前面暴力解法中的第一个for循环就是起始位置,第二个for循环就是结束位置
具体实现过程
- 快指针右移:慢指针不冻,快指针右移,sum求和快慢指针之间数字并与target比较大小,直至sum的大小>=target时停止
- 慢指针右移:当快指针停止时,慢指针逐步右移,逼近最短距离min_len(局部),然后再启动快指针右移,寻找下一个min_len
- 循环上述过程直至找到全局的min_len
代码
int minSubArrayLen(int target, vector<int> &nums)
{
// 滑动窗口法
int slow = 0;
int fast = 0;
int sum = 0;
int min_len = nums.size() + 1;
while (fast < nums.size())
{
sum += nums[fast];
// 当sum大于等于target时,尝试缩小窗口
while (sum >= target)
{
int len = fast - slow + 1;
// 更新最小长度
if (min_len > len)
{
min_len = len;
}
sum -= nums[slow];
slow++;
// 当sum小于target时,缩小到最小长度了,跳出循环
if (sum < target)
{
break;
}
}
fast++;
}
// 如果min_len等于nums.size() + 1,说明没有找到符合条件的子数组,返回0
return min_len == nums.size() + 1? 0 : min_len;
}
螺旋矩阵
题目链接:https://leetcode.cn/problems/spiral-matrix-ii/
文章讲解:https://programmercarl.com/0059.%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5II.html
视频讲解:https://www.bilibili.com/video/BV1SL4y1N7mV/
自己是第二次做这个题了,第一次是在洛谷上做的,第一次整半天都不会,第二次好多了
思路
考察的是模拟。我这次对这题的第一想法是递归(其实是因为写递推怕写错,算法菜狗落泪),因为每一轮顺时针转圈都是同样的操作,并且下一轮的转圈其实只需看成在一个新的被裁剪掉最外圈的矩阵上的最外圈转(有点绕)。
递归实现
每次矩阵都从start处开始顺时针转圈,并且每条边都只走size-1个格子,留下一个给下一个开头
-
上边
矩阵行下标固定为start,从矩阵列下标为start处开始向右走到start+size-2 (start+size-1刚好为最后一列/行),留下start+size-1当做右边的开头
-
右边
矩阵列下标固定为start + size -1,行下标从start向下走到start+size-2
-
下边
同理,行固定为start+size-1,列从start+size-1到start-1
-
左边
行从start+size-1到start-1,固定为start
-
递归
start+1进入下一个矩阵,size-2(缩小一圈,减小两行/列)
结束条件
1.n为奇数,则最后会缩到一个size=1的单元格,此时赋上值后结束
2.n为偶数,则最后转完最后一圈后size=0,直接结束
当然理解了以上内容可以发现递归完全没有必要,直接for循环递推就行
代码
void fill(vector<vector<int>> &matrix, int start, int size, int num)
{
//终止条件
if(size < 0) return;
if(size == 1) matrix[start][start] = num;
//start + size - 1 表示最后一行/列的下标
//每行填充 size - 1个数,最后一个数留给下一条边的开头
//上边
for(int i = 0; i < size - 1; i++)
{
matrix[start][start + i] = num;
num++;
}
//右边
for(int i = 0; i < size - 1; i++)
{
matrix[start + i][start + size - 1] = num;
num++;
}
//下边
for(int i = 0; i < size - 1; i++)
{
matrix[start + size - 1][start + size - 1 - i] = num;
num++;
}
//左边
for(int i = 0; i < size - 1; i++)
{
matrix[start + size - 1 - i][start] = num;
num++;
}
//递归填充
fill(matrix, start + 1, size - 2, num);
}
vector<vector<int>> generateMatrix(int n) {
//螺旋矩阵
vector<vector<int>> matrix(n, vector<int>(n));
fill(matrix, 0, n, 1);
return matrix;
}
for循环递推版本
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
int t = 0; // top
int b = n-1; // bottom
int l = 0; // left
int r = n-1; // right
vector<vector<int>> ans(n,vector<int>(n));
int k=1;
while(k<=n*n){
for(int i=l;i<=r;++i,++k) ans[t][i] = k;
++t;
for(int i=t;i<=b;++i,++k) ans[i][r] = k;
--r;
for(int i=r;i>=l;--i,++k) ans[b][i] = k;
--b;
for(int i=b;i>=t;--i,++k) ans[i][l] = k;
++l;
}
return ans;
}
};
数组总结
二分法
- 注意边界条件
- 两种写法:左闭右闭,左闭右开
双指针法
- 用快慢指针在一个for循环干了两个for循环的活
- 双指针就好像你的两根手指头指着数组的不同地方,可以同时同空间进行两件事情
滑动窗口
- 类似于剪辑中的截取片段,不断调整窗口的起始位置和末尾位置来达到目标
- 一般是先移动end,再移动start
模拟
严格抓住循环不变量(统一的区间定义,转移方式),例如螺旋矩阵中的左闭右开原则(只走size-1个格子)就是在一轮大循环中的不变量,每一条边都遵循此原则那么逻辑非常清晰