代码随想录算法训练营day2| 977.有序数组的平方 209.长度最小的子数组 59.螺旋矩阵II

本文探讨了在LeetCode编程挑战中,如何运用双指针方法、区间定义以及滑动窗口优化解决方案,如有序数组平方、长度最小子数组和螺旋矩阵II等问题,强调了数组下标处理和边界条件的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

是对第一天双指针写法还有区间定义的一些进一步练习。

总体来说难度还是有一些的。加之有不少细节的地方需要处理。

977.有序数组的平方

977. 有序数组的平方 - 力扣(LeetCode)

读完题第一感觉就是暴力循环,然后把整个数组排序就行。如果不用暴力方式又该用什么思路呢?

数字的绝对值越大,平方数就越大。

虽然知道了上面这点,但是如何试图在一次遍历就形成一个新的按要求排序的数组呢?

因为数组是非递减顺序排序的,可以观察出数组两端的平方数肯定比中间的平方数大,这样一来,可以考虑用双指针法从两端向中间移动,并且保证每次取到一个最大的平方数,再反向遍历形成一个新数组。

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int k = nums.size() - 1;
        vector<int> res;
        res.resize(nums.size()); //初始化和nums一样大的res数组
        for (int i = 0, j = nums.size() - 1; i <= j;) {//i=j时说明两个指针指向了同一元素,用i<=j确保不会漏掉每一个元素
            if (nums[i] * nums[i] >= nums[j] * nums[j]) {
                res[k] = nums[i] * nums[i];
                i++; //赋值后指针才移动
                k--; //反向遍历
            } else {
                res[k] = nums[j] * nums[j];
                j--;
                k--;
            }
        }
        return res;
    }
};

双指针移动的时候需要注意的是对元素赋值后指针才移动,指针指向了某个下标,应当先完成了一些操作再移动指针。

209.长度最小的子数组

209. 长度最小的子数组 - 力扣(LeetCode)

读完题第一感觉是和下标移动有关,也自然会想到两层循环暴力遍历的解法。

不过要优化这种解法,试图使用一次遍历解决这个问题,也联想到了双指针。

数组中还有一个重要的操作叫滑动窗口,通过不断调整起始位置和终止位置,使得在答案区间内的窗口不断向前移动来找到最优解,本质上还是对两个指针的一些操作。

本体思路大致如下:

终止位置不断向前移动去寻找合适的答案区间,窗口用来收集满足答案的区间值;

等到窗口值满足了目标值(也就是满足其元素和>=目标值),起始位置再向前移动,去寻找最优解。

其间用一个变量去记录每一次满足条件的子数组长度,最后取出长度最小的。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int sum=0,ans=0x3f3f3f3f,flag=0;
        for(int i=0,j=0;i<=j,j<nums.size();){
            sum+=nums[j];
            while(sum>=target){
                flag=1;
                int res=j-i+1; //记录子数组长
                ans = res<=ans?res:ans;//每次保持最短长度的答案
                sum-=nums[i];
                i++;
            }
            j++;
        }
        if(flag) return ans;
        else return 0;
    }
};

遍历过程中同样要注意先对窗口进行完条件判断的操作,再移动指针

59.螺旋矩阵II

59. 螺旋矩阵 II - 力扣(LeetCode)

是一道模拟题,并没有什么特定的算法,但是很考验边界条件处理。在每一条边的遍历都应该统一使用一种区间定义。

我选择了以左闭右开的区间去计算出矩阵

题目是nxn的矩阵,而每顺时针旋转一圈,就会减少上下两边和左右两边,所以当n/2次循环(也就是整个矩阵边长的一半)就可以遍历完整个矩阵

*n为奇数时,会发现还差最中心一个元素没有处理到,加一个特殊判断补上。

定义一个res数组,用来记录矩阵,注意数组下标从0开始。

以n=3时为例,最外圈的最上面一层下标为0-2,那么以左闭右开区间来描述就是[0,3);

接下来考虑如何表示循环的起止条件:每轮循环有四条边要进行操作,每条边都有起始点和终止点,使用左闭右开的写法,则前一条边的终止点就是下一条边的起始点。而每轮循环起始点位置就是主对角线(下标为00 11 22 33 ……的那条对角线)

初始化flag=1,则n-flag可以表示终止点,flag-1表示起始点。

(这里的推导画图可以形象地得出这个规律,如果分辨不清起始点可以设两个变量startxstarty遍历一轮之后观察一下)

每轮循环按四个方向遍历,经过一轮后用flag++更改起止点为止(表示更深入一层的遍历)

*n为奇数时需补充最中心的一个元素,即补充一个主对角线元素,也就是完成这几圈遍历后的下一个起止点的位置。

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>>res (n,vector<int>(n,0));
        int flag=1,cnt=1;
        int i,j;
        int t=n/2;
        while(t--){
            i=j=flag-1;
            //每轮起始点进行一下初始化
            for(;j<n-flag;j++) res[i][j]=cnt++;
            for(;i<n-flag;i++) res[i][j]=cnt++;
            for(j=n-flag;j>flag-1;j--) res[i][j]=cnt++;
            for(i=n-flag;i>flag-1;i--) res[i][j]=cnt++;
            flag++;
        }
        if(n%2) res[flag-1][flag-1]=n*n;
        return res;
    }
};

总结

  1. 数组的下标是从0开始的,且内存连续,对数组进行元素删除时实际上是在进行元素覆盖

  2. 二分法要注意边界条件,常见的区间条件有左闭右开和左闭右闭。要根据不同区间定义来写判断条件

  3. 双指针和滑动窗口是对下标值的一些控制,实现对于暴力解法的优化。一般是观察数组的下标值,找到一定规律来移动两个指针去实现。

(后续时不时会进行一些拓展题目的补充,欢迎大家一同讨论;文章有不妥之处也欢迎指出。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值