算法学习 | day2/60 有序数组的平方

一、题目打卡

        1.1 有序数组的平方

        这个题目虽然有提示是使用双指针对问题进行求解,但是拿上手的时候没有具体想到双指针怎么使用,所以算是使用了暴力的解法吧,采用的快速排序的方法对计算出来的结果直接进行排序,也当做是对之前快速排序的方法进行复习:

class Solution {
private:
    int partation(vector<int> &input,int l, int r){
        int tmp = input[l]; // 哨兵
        int i = l, j = r;
        if(i >= j) return i;
        while(i < j){
            while(i < j && input[j] >= tmp) j--;
            while(i < j && input[i] <= tmp) i++;
            swap(input[i],input[j]);
        }
        swap(input[l],input[i]);
        return i;
    }
    void fastSort(vector<int> &input, int l, int r){
        if(l >= r) return;
        int res = partation(input, l, r);
        fastSort(input, l, res - 1);
        fastSort(input, res + 1, r);
    }

public:
    vector<int> sortedSquares(vector<int>& nums) {
        // 检测算法是否正确
        // vector<int> input{5,4,9,2,1,3,7};
        // fastSort(input, 0, 6);
        // for(auto &it : input){
        //     cout << it << " ";
        // }
        // return input;
        for(int i = 0; i < nums.size();i++){
            nums[i] = nums[i] * nums[i];
        }
        fastSort(nums,0,nums.size()-1);
        return nums;
    }
};

        接着看了答案,对双指针有了更进一步的理解,就目前的认知来看,双指针的使用貌似就是从两头向中间,要不就是设置快慢指针,看了答案解析以后,自己写的,并且犯了一个小错:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        //双指针的思想
        int i = 0, j = nums.size() - 1, ind = nums.size() - 1;
        vector<int> res(nums.size(),0); // 提前创建好方便进行索引
        //这里的关键思想是最大值只有可能在两端产生
        while(i < j){
            if(nums[i] * nums[i] >= nums[j] * nums[j]){
                res[ind] = nums[i] * nums[i];
                ind--;
                i++;
            }else{
                res[ind] = nums[j] * nums[j];
                j--;
                ind--;
            }
        }
        // 处理 i == j 的最后一个数
        /* debug
        cout << "i=" << i << "j=" << j << "nums[i]=" << nums[i] * nums[i];
        cout <<endl;
        for(auto &it : nums){
            cout << it << " ";
        }
        */
        // 手滑写成判断语句了
        // res[ind] == nums[i] * nums[i];
        res[ind] = nums[i] * nums[i];
        return res;
    }
};

​​​​​​​        感想:

        这里的感想首先是对于 while 循环的条件,如果很多时候没有办法准确判断 i 和 j 的关系,那么就思考一下,等于的时候是否是两者需要结束的时候,这里以快速排序为例,当等于的时候,也就相当于所有和哨兵比较的数都已经交换完毕,那么就已经可以结束循环了,而对于平方最大值来说,其实都可以,如果写成 i < j 的话,需要自己在最后进行单独数据的一个处理。

        1.2 长度最小的子数组

        这个题目在一开始看到的时候,提醒使用滑动窗口进行求解,接着自己思考写了下面的代码,在写的过程还是存在一些问题,基本都是通过 debug 找到问题所在:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        // 这里是比小,因而min_len需要初始化一个很大的数
        int min_len = INT_MAX, i = 0, j = 0, sum = 0;
        while(true){
            if(sum < target) sum += nums[j++];
            else{
                min_len = min_len < j - i ? min_len : j - i;
                sum -= nums[i++];
            }
            if(j >= nums.size() && sum < target) break;
        }
        return min_len == INT_MAX ? 0 : min_len;
    }
};

        当往滑动窗口进行思考的时候,我将其分为了三个过程,分别维护了四个变量,min_len用于动态更新数组比较过程中的最小长度,i , j 分别代表滑动窗口的区间,这里我计算的时候是将其作为闭区间处理的,最后 sum 用于动态更新统计出来的滑动窗口内数的大小,接着思路如下:

        1)当滑动窗口的和比target小的时候,就不断增加滑动窗口的右端,并动态更新 sum;

        2)当滑动窗口的和不小于target的时候,这时候就可以开始统计窗口的长度了,这里有一个小坑一开始自己犯的,因为比较的过程是比小,因而滑动窗口的和的数值一开始应该更新为无限大,在更新完 min_len 以后,将左端向右边移动,并减去左端的值。

        3)如此一直更新到 j 到达整个数组的右端时,此时的关键是观测 sum 是否比 target 小,如果否,那么就一直缩小左端,此时有一个关键,自己 debug 时候发现的,就是 sum < target,这个条件在一开始我思考到了,但是发现这个条件不能带等号,因为带等号也就说明,此时区间还可以进一步缩小,这里就和此时 j 的索引始终是比 nums.size() 大有关,也就是相当于是开区间。

        看了随想录的解析,和自己思路差不多,这里就直接把随想录的代码贴在这里:

class Solution {
public:
    int minSubArrayLen(int s, 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,每次更新 i(起始位置),并不断比较子序列是否符合条件
            while (sum >= s) {
                subLength = (j - i + 1); // 取子序列的长度
                result = result < subLength ? result : subLength;
                sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }
};

        1.3 生成螺旋矩阵

        这个题自己写的过程挺折磨的,很久之前自己做过,然后凭借记忆重新写了一遍,但是遇到了很多细节上的问题,关键是,为什么这个 vscode 不能 debug 啊?代码有问题的时候,vscode 的 debug 也会出错,导致电脑还炸了几次,暂时没有找到为什么,关键还是代码不能写错(但是我真的想吐槽这个 debug,debug 不就是为了找错误吗,代码对了还 debug 干啥)。

        这是代码其中的一个版本:

#include<iostream>
#include<vector>
using namespace std;

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<int> tmp(n,0);
        vector<vector<int>> res(n,tmp);

        // 模拟四个天花板 left, right, top, bottom, 想象是一个盒子进行包裹
        int l = 0, r = n, t = 0, b = n;
        int i = 1;
        while(t < b && l < r){
            int it = l;
            while(l < r && it < r) res[t][it++] = i++;
            // 第一个出现错误的地方,我一开始把t++,写成了t--,下面的l++也是
            t++;
            it = t;
            // 第二个错误,我把 it < b 这个条件写成了 it > b
            while(t < b && it < b) res[it++][r - 1] = i++;
            r--;
            // 第三个错误的地方,我把这里赋值的语句忘记减1了,但实际由于右侧是开区间,这里还需要减一个1
            it = r - 1;
            // 最后就是 it >= l 这里,因为 l 和 t 是闭区间,因而这里写终止条件的时候,需要加等号
            while(l < r && it >= l) res[b - 1][it--] = i++;
            b--;
            it = b - 1;
            while(t < b && it >= t) res[it--][l] = i++;
            l++;
        }
        // 用来检查对不对的
        // for(auto &i : res){
        //     for(auto &j : i){
        //         cout << j << " ";
        //     }
        //     cout << endl;
        // }
        
        return res;
    }
};

int main(int argc, char *argv[])
{
    int n = 3;
    Solution s;
    s.generateMatrix(n);
    return 0;
}

        整体的思路没有问题,就是设置四个边界不断缩小来进行输出,但是这里面我在一开始的这些地方出现了错误,在代码中都标识出来了。

        感想:

        这里主要是对开区间和闭区间的后终止条件进行了进一步学习,有了更深入的认识,其实本身来说,这个题用双闭区间会好一点,如果右边是开区间,那就需要注意后面判断条件的使用,并且在动态更新 it 的时候,也会好处理一点,还有一个需要注意的小细节就是对于 it 以及各个天花板的的变化,方向不要搞错了,不然会导致越界。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值