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

题目链接:leetcode 977、有序数组的平方

文章讲解:代码随想录 977、有序数组的平方讲解

视频讲解:双指针法经典题目 | LeetCode:977.有序数组的平方

自己看到题目的第一想法

题目: 给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
想法: 这个我自己第一次做的时候忽略了一个点就是数组里可能有负数,所以把问题想的很简单,直接每个元素平方放回原位置就可以了,只需要遍历一次数组即可,后来运行一看原来有负数,于是就加了一行快速排序,就顺利通过了,没有再思考双指针,直接去看讲解了。

我的做法的时间复杂度取决于快速排序的时间复杂度,也就是 O ( n l o g n ) O(n logn) O(nlogn)

自己写的代码:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int n = nums.size();
        for(int i=0;i<n;i++){
            nums[i] = nums[i] * nums[i];
        }
        sort(nums.begin(), nums.end());
        return nums;
    }
};

看完代码随想录和大家探讨之后的想法

解题思路还是比较简单明确的,由于负数存在,平方后数组不一定是有序的,可能原本排在数组左侧的负数平方大于数组右侧的数字的平方,于是利用双指针,从两端开始向数组中间遍历,每次对比两端平方值,取小值存入结果数组。

考虑以下循环终止条件,那就是左指针大于右指针,因为相等时也要进行一次处理。
还有就是要单开一个结果数组,我看完讲解自己写代码时就忽略了这一点,还按照自己一开始快速排序的习惯直接在原数组上赋值,导致数组直接乱了,debug才找出来的。

自己写的代码,和讲解里几乎一样,有点小区别:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int k = nums.size()-1;
        vector<int> result(k+1,0);
        for(int i=0,j=nums.size()-1;i<=j; ){
            if(nums[i]*nums[i] < nums[j]*nums[j]){
                result[k--] = nums[j]*nums[j];
                j--;
            }else{
                result[k--] = nums[i]*nums[i];
                i++;
            }
        }
        return result;
    }
};

这里不想再纠结特别细节地方的写法了,应该影响不大,重点练习自己思考的思路,不强行记别人的写法。

题目链接:leetcode 209、长度最小的子数组

文章讲解:代码随想录 209、长度最小的子数组讲解

视频讲解:拿下滑动窗口! | LeetCode 209 长度最小的子数组

自己看到题目的第一想法

题目: 给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
想法: 我自己思考也是考虑到了一个滑动的窗口,但是自己实现起来在细节上遇到了困难,最终没有通过,先贴上自己写的没通过的代码,然后反思一下问题出在哪里。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int length = 0;
        int n = nums.size();
        int sum=0;
        for(int i=0,j=0;i<n&&j<n;){
            sum += nums[j];
            if(sum >= target){
                if(length==0){
                    length=j-i+1;
                    i++;
                    j++;
                }else{
                    length = min(length, j-i+1);
                    i++;
                    j++;
                }
            }else{
                j++;
            }
        }
        return length;
    }
};

和第一天写双指针犯了类似的错误,就是循环条件没有设置好,虽然不完全一样吧。
这里总结以下双指针的重点:
1、如果是快慢指针,比如这道题的滑动窗口就是一种快慢指针,进行遍历的时候以快指针为循环条件就可以了,因为要遍历数组一定是每一次都要继续遍历,快指针一定要向下走,慢指针不一定,所以用快指针当条件即可,在循环体里再判断慢指针要不要动。
2、第二种情况就是上一道题这种,双指针从数组两端向中间遍历,这种情况就要拿两个指针一起做循环条件,一般结束循环的条件都是左指针跑到了右指针的右边,注意以下要不要含等号即可,一般应该是左指针大于右指针结束,等于的时候是需要处理的,不然这个元素没访问到。

有了以上总结,其实自己思路就不会产生那么多复杂的问题,循环条件正确了,这道题剩下的很顺利就可以考虑出来。所以这里就不继续说自己当时复杂又错误的思路了。

看完代码随想录和大家探讨之后的想法

暴力解法就不提了,现在看到这种问题,即使是自己思考也不会想着暴力解法了,给人的感觉非常奇怪,其实暴力解法不符合我们人解决问题的思路,我认为我们最初做算法题思考总想到暴力解法的原因是我们想到一个逻辑上稍稍复杂的东西,但是我们不会用代码实现,因为一开始在代码实现上有短板,所以会想到学习时老师提到的计算机是傻的,但是计算能力强,于是就循环递归那样搞,这样其实反而阻碍了我们解决问题的思路,感觉优点可惜。。。

接下来就说一下滑动窗口的双指针解法。要找满足条件的区间,前文提到这种快慢指针以快指针为循环条件,指针我们就设为i和j好了,那么就想一下循环体里我们要做几件事:
1、要判断窗口内数字和是否大于target,所以每次都要累加新的值进来,那么设一个累加值sum,每次都把快指针指向的值加上
2、判断sum是否大于target,如果大于说明可能是最短子区间,这里又要在前面设置一个result,记录最短子区间,给最短子区间赋值时要加一个判断,这一次满足条件的子区间是不是最短,所以用std::min函数,result的初始值也要设置为最大值
这里说一下我原本不熟悉的知识点就是INT32_MAX,这个是从讲解里学到的,另外竟然还可以判断一个值是否等于INT32_MAX,我自己写会怀疑这个事情,总会想到什么inf、nan这种东西,无所谓了。
3、还有一处细节,处理过后,慢指针要移动,所以考虑移动慢指针都需要做哪些处理,慢指针移动前要把sum减一下慢指针的值,然后再移动慢指针,另外直接在这里考虑慢指针什么时候停下,而不是慢指针只移动一次。那么慢指针什么时候停下呢,一定是sum值小于target再停下,因为下一次循环,快指针会移动并且sum添加了新的快指针的值,所以一定要让慢指针移动到sum小于target时停下,这个用while实现,同时第2步和第3步处理还需要都放在这个while里。

时间复杂度 O ( n ) O(n) O(n),因为只有一个for循环,里面的while不是全部循环,都是有限次数的操作,从另一个角度考虑就是讲解里说的,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 2 × n 2×n也就是 O ( n ) O(n) O(n)

自己写的代码,和讲解差不多:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int n = nums.size();
        int result = INT32_MAX;
        int i = 0;
        int sum = 0;
        for (int j = 0; j < n; j++) {
            sum += nums[j];
            while (sum >= target) {
                result = min(result, j - i + 1);
                sum -= nums[i];
                i++;
            }
        }
        return result == INT32_MAX ? 0 : result;
    }
};

最后一句表达式写法是从讲解里学来的,虽然读代码见过,但是自己目前想不到这个写法。

题目链接:leetcode 59、螺旋矩阵Ⅱ

文章讲解:代码随想录 59、螺旋矩阵Ⅱ讲解

视频讲解:一入循环深似海 | LeetCode:59.螺旋矩阵II

自己看到题目的第一想法

题目: 给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
想法: 看题目里的图会有种要这样给矩阵赋值的感觉,但是

看完代码随想录和大家探讨之后的想法

关键点其实就是视频讲解里说的循环不变量部分。第一个要想到的就是怎么循环,要一圈一圈来,然后是一圈四个边赋值要用左闭右开区间这种想法,个人感觉这个题目还是需要经验的,没什么好说的。

自己写的代码:

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> result(n, vector<int>(n, 0));
        int offset = 1;
        //每圈的起始坐标 由于一直都是遍历一圈正方形,所以起始位置行列坐标是相同的,用一个即可
        int start = 0;
        //要填入的数字,由于填一圈遍历时,数字与容器下标难以直接对应,所以单独记录要填入的数字
        int count = 1;
        //一圈一个循环,每填完一圈,对一个边来说就少了两个端点,如果n是偶数,一共要n/2个循环,如果是奇数要n/2+1个循环,奇数最后一次赋值就是矩阵中心位置
        for (int ncount = 0; ncount < n / 2; ncount++) {
            //填上方一行
            for (int j = start; j < n - offset; j++) {
                result[start][j] = count++;
            }
            //填右方一行
            for (int i = start; i < n - offset; i++) {
                result[i][n - offset] = count++;
            }
            //填下方一行
            for (int j = n - offset; j > start; j--) {
                result[n - offset][j] = count++;
            }
            //填左方一行
            for (int i = n - offset; i > start; i--) {
                result[i][start] = count++;
            }
            //填完一圈更新起始坐标
            start++;
            offset++;
        }
        //中心点赋值
        if (n % 2 == 1) result[n / 2][n / 2] = n * n;
        return result;
    }
};

和文章讲解有点不一样,小问题,讲解的我能看懂,但是对 i i i j j j的处理可能不太符合我自己思考的习惯,我自己写的这个也稍稍debug了一下,中间四个for循环还是需要思路清晰点才能顺利写出来,这个代码写的时候加了注释,就不再解释了。

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值