977.有序数组的平方
双指针法经典题目 | LeetCode:977.有序数组的平方
状态:只会使用暴力解法,双指针法没有想出来
思路
方法一:暴力解法
使用一个for循环直接把数组元素全部平方,之后再对数组排序
代码实现:
class Solution {
public:
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;
//时间复杂度o(nlogn)空间复杂度o(1)
}
};
显然时间复杂度为o(n+nlogn)
方法二:双指针法
数组是有序的,就是平方之后负数可能变成最大值了,也就是说最大值在数组的两边,可以用双指针,i,j,分别指向数组的起始和终止位置,然后用空间换时间策略,定义一个和nums一样大的数组,用来接收nums数组元素平方后的值
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
//双指针解法
int i=0,j=nums.size()-1,k=nums.size()-1;
vector<int> result(nums.size(),0);
while(i<=j){//注意是小于等于 如果是小于的话那么就会少处理一个元素
if(nums[i]*nums[i]>nums[j]*nums[j]){
result[k--]=nums[i]*nums[i];
i++;
}
else{
result[k--]=nums[j]*nums[j];
j--;
}
}
return result;
}
};
显然整个过程是对所有的元素操作了一遍 因此时间复杂度o(n)空间复杂度o(n),采用了一个用空间换时间的策略。
209.长度最小的子数组
拿下滑动窗口! | LeetCode 209 长度最小的子数组
方法一:暴力解法
使用两层for循环,遍历所有可能出现的子数组,然后依次对比,取出符合条件的数组
代码实现:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
//暴力解法 遍历所有可能出现的子数组
int minlen=nums.size()+1;
for(int i=0;i<nums.size();i++){
int sum=0;
for(int j=i;j<nums.size();j++){
sum+=nums[j];
int len=j-i+1;
if(sum>=target&&len<minlen){
minlen=len;
break;
}
}
}
if(minlen>nums.size())return 0;
return minlen;
//时间复杂度o(n方)超时
}
};
暴力解法有一个很明显的缺陷,即多做了很多不必要的操作,例如一个子数组加和已经比s小了,但是暴力解法仍然会处理该子数组的子数组,很显然这个操作很多余,所以思路就是如何把这些多余的操作给去掉,这时就轮到滑动窗口登场了。
方法二:滑动窗口
思路:外层循环控制终止位置,起始位置动态调整,(什么时候调整? 当起始位置到终止位置这个区间内的元素的和比target大的时候就可以调整起始位置了,向后移动一位,这个时候再看起始位置到终止位置这个区间的元素和还比target大不大,如果还大就一直调整,边调整边更新滑动窗口的长度,最后取滑动窗口长度最小的值)逐渐调整终止位置,直到终止位置遍历完一遍数组,这个时候取出滑动窗口的最小值即为所得结果。
代码实现
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
//滑动窗口
int result=INT32_MAX;
int sum=0;//滑动窗口的和
int i=0;//滑动窗口的起始位置
int sublength=0;//滑动窗口长度值
for(int j=0;j<nums.size();j++){
sum+=nums[j];
while(sum>=target){
sublength=j-i+1;//更新滑动窗口的长度
sum-=nums[i];//因为相当于去掉窗口中的第一个元素了,所以这里的和要减掉
i++;
if(result>sublength) result=sublength;//更新最小子区间长度
}
}
return result==INT32_MAX?0:result;
}
};
时间复杂度o(n) 因为外层循环对数组的每个元素操作了一次,内层while对每个元素操作一次,总共操作2*n次 所以时间复杂度为o(n)
59.螺旋数组(二)
59.螺旋矩阵II
比较考验代码能力,同时通过这个题可以再次回顾一次二叉查找中提到的循环不变量。
思路
关键在于坚持循环不变量的原则,保证每一层的处理逻辑相同,一步一步模拟画矩阵的过程:1.填充上行从左到右,保证边界左闭右开(这样空出来的一个元素就可以留给下一个将要处理的行使用,下一个待处理行也是左闭右开),2.右行从上到下,同理保证边界左闭右开,3.下行从右到左。4.左行从下到上;
代码实现:
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>>res(n,vector<int>(n,0));//定义一个二维数组
int startx=0,starty=0;//定义每层循环的起始位置
int loop=n/2;//每个圈循环几次,例如n=3,循环2次,n=5循环3次,可以推出规律
int offset=1;//需要控制每一条边遍历的长度,每次循环右边界收缩一位
int mid=n/2;//矩阵的中间位置,例如n=3.中间位置为(1,1)n=5中间位置为(2,2)
int count=1;//用来给矩阵每个格赋
int i,j;
while(loop--){
i=startx;
j=starty;
//填充上行从左到右
for(j=starty;j<n-offset;j++)
res[startx][j]=count++;
//填充右行从下到上
for(i=startx;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,eg:第一圈的起始位置是(0,0)第二圈的起始位置应该是(1,1)
startx++;
starty++;
//控制每一圈里每一条边遍历的长度,每加一圈其实相当于长度减2,因为往里缩一圈,左右两边都会缩一格,左边起始位置加了,右边用offset控制,其他边类似
offset+=1;
}
//如果n为奇数,则最内层需要单独处理
if(n%2!=0){
res[mid][mid]=count;
}
return res;
}
时间复杂度o(n^2)因为处理了n*n个元素,空间复杂度o(n^2)
总结
今天的三个题相比昨天难度有所提升,昨日的27题让我把双指针法入门了,今天的977算是对双指针法思想的补强,本题关键在于考虑到数组平方后,最大值只会出现在数组两边,最小值会出现在数组中间,数组元素大小向内收敛。209开拓了思维,掌握了一种全新的算法思想,即滑动窗口思想,滑动窗口关键在于理解for循环中的指针指向的是数组的终止位置,而数组的起始位置要根据实际需求动态的调整,比起暴力解法,该解法减少了暴力解法中很多不必要的操作。59题算是把循环不变量体现到极致了,注意处理每一条边的时候要把握住循环不变量的原则,同时对于每一圈的起始位置,以及每一条边的变化也要提高警惕,例如每向内缩一圈其实边的长度是减少了2,因为边头和边位均向内缩了一位,所以offset和startx和starty都需要加一。