代码随想录算法训练营第二天|977.有序数组的平方、209.长度最小的子数组、59.螺旋矩阵(二)

977.有序数组的平方

977.有序数组的平方

代码随想录(programmercral.com)

双指针法经典题目 | 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.长度最小的子数组

209.长度最小的子数组

代码随想录(programmercral.com)

拿下滑动窗口! | 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​​​​​​​

代码随想录(programmercral.com)

一入循环深似海 | LeetCode: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都需要加一。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值