代码随想录算法训练营第2天|LeetCode977.有序数组的平方、LeetCode209.长度最小的子数组、LeetCode:59.螺旋矩阵II
1、LeetCode977.有序数组的平方
题目链接:977. 有序数组的平方 - 力扣(LeetCode)
文章链接:代码随想录 (programmercarl.com)
视频讲解:双指针法经典题目 | LeetCode:977.有序数组的平方_哔哩哔哩_bilibili
第一想法
直接算每一个的平方,最后sort排序。时间复杂度O(nlogn)。
vector<int> sortedSquares(vector<int>& A) {
for (int i = 0; i < A.size(); i++) {
A[i] *= A[i];
}
sort(A.begin(), A.end()); // 快速排序
return A;
}
解法
发现平方后的数组是大小是两端大,中间小。因此可以使用两个指针从两边开始遍历,比较大小,更大的元素放入新数组并更新指针。
更新新的数组是下标从大到小即可实现新数组从小到大排序。
时间复杂度O(n)。
代码
注意循环条件是i<=j,若写成i<j,那么会漏掉i=j这个元素。
vector<int> sortedSquares(vector<int>& nums) {
int size=nums.size();
vector<int> result(size);
// 对元素平方
for(int i=0; i<size; i++)
nums[i]=nums[i]*nums[i];
int k = size-1;//result从最后一个开始填
int left=0,right=size-1;
while(left <= right){
if(nums[left] > nums[right]){
result[k] = nums[left++];
}
else{
result[k] = nums[right--];
}
k--;
}
return result;
}
2、LeetCode209.长度最小的子数组
题目链接:209. 长度最小的子数组 - 力扣(LeetCode)
文章链接:代码随想录 (programmercarl.com)
视频讲解:拿下滑动窗口! | LeetCode 209 长度最小的子数组_哔哩哔哩_bilibili
第一想法
两层循环,一层遍历子数组开头i,一层遍历子数组结尾j从i开始,找到sum>=target的len=j-i+1。
代码:
犯错:一开始设置j=i+1,没有考虑道子序列长度为1的情况。
力扣更新了数据,暴力算法超时。
int minSubArrayLen(int target, vector<int>& nums) {
int maxint = 100005;
int size=nums.size();
int res=maxint;
int sum=0;
for(int i=0; i < size; i++){ //开头
sum = 0;
for(int j = i; j < size; j++){ //结尾
sum += nums[j];
int temp = j-i+1;
if((sum == target || sum > target) && temp<res){
res = temp;
break; //后面的肯定都>=target,但是长度都不是最小
}
}
}
if(res == maxint)
return 0;
return res;
}
滑动窗口解法
思路:
只用1个循环。循环的条件j指的是终止位置。当找到sum>=target的j时,移动起始位置i来找有没有更小的子序列长度len。
复杂度:
O(n)。不要以为for里放一个while就以为是O(n^2)。主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。
重点:如何移动起始位置i。
注意:是while循环而不是if,因为是持续向后移动的过程,例如[1, 1, 1, 1, 1, 100], target为103,若为if循环,起始位置移动之后仍>=target,但已经跳出if了,该终止位置继续移动。因此应为while,起止位置持续往后移动。
代码:
int minSubArrayLen(int target, vector<int>& nums) {
int res = 100005;
int i=0; //起始位置
int j=0; //终止位置
int sum=0;
for(int j=0;j< nums.size() ; j++){
sum += nums[j];
while(sum > target || sum == target){
int templen = j-i+1;
res = templen < res ? templen : res;
sum -= nums[i];
i++; //移动起始位置
}
}
if(res == 100005)
return 0;
return res;
}
新的思路:
我想起来算法设计与分析课上讲的“最大子序列之和”这道题,用动态规划,从最后一个元素开始。忘了…有空回去看看…
3、LeetCode:59.螺旋矩阵II
题目链接:59. 螺旋矩阵 II - 力扣(LeetCode)
文章链接:代码随想录 (programmercarl.com)
视频讲解:一入循环深似海 | LeetCode:59.螺旋矩阵II_哔哩哔哩_bilibili
面试高频,没有什么算法,单纯模拟转圈的过程。
难点:每一圈的四个顶点怎么处理,给谁处理。
思路
循环不变量:对每条边的处理规则,左闭右开,只处理第一个节点,不处理最后一个节点。
三个每圈都要更新的变量:
- startx
- starty
- offset:每条边终止位置需要维护这样一个变量。
while(圈数):n/2,如果是奇数,最中间的值可以最后赋值。
代码
犯错
- 二维vector定义;
- 对上行赋值时for循环重新定义了j,导致出来后j重新变为starty,就出错了。
vector<vector<int>> generateMatrix(int n) {
int loop = n/2;//有几圈
int startx=0;//每一圈的起始位置
int starty=0;
int offset=1;
int count=1;//用以赋值
//二维vector定义,记得0初始化
vector<vector<int>> ans(n,vector<int>(n, 0));
int i,j;
while(loop--){
//初始化
i = startx;
j = starty;
//上行
for(j = starty; j<n-offset ; j++){ //注意!!j不能定义循环里面,这里不用重新定义j,否则出来就丢失了
ans[startx][j] = count++;
}
//右边列
for(i=startx ; i<n-offset ; i++){
ans[i][j] = count++;
}
//下边行
for( ; j > starty ; j--){
ans[i][j] = count++;
}
cout<<endl;
//左边列
for( ; i > startx ; i--){
ans[i][j] = count++;
}
// 更新
startx++;
starty++;
offset++;
}
if(n%2 != 0){//奇数
ans[n/2][n/2] = count;
}
//输出
// for(int i=0;i<n;i++){
// for(int j=0;j<n;j++){
// cout<<ans[i][j]<<" ";
// }
// cout<<endl;
// }
return ans;
}
4、总结:
- sort排序写法
sort(A.begin(), A.end())
- vector二维数组定义方法
- 贯穿整个代码的循环条件,不要在for里面重新定义!!