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

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

#977.有序数组的平方
977.有序数组的平方
第一次尝试

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        //因为是vector 所以觉得可以用sort
        //感觉也可以用一个单调队列 又有点麻烦
        for(int i=0;i<nums.size();i++)
        {
            int temp=nums[i];
            nums[i]=temp*temp;
        }
        sort(nums.begin(),nums.end());
        return nums;
    }
};

但是这个肯定不是想让我用sort函数
顺便复习了一下sort

 // 使用sort函数对vec进行排序  
    std::sort(vec.begin(), vec.end());  

并且sort默认是升序排序 刚好是对的 如果想要降序排序 需要自定义boot compare函数或者用lambda函数(不会)((〃>皿<))

// 自定义比较函数,用于降序排序  
bool compare(int a, int b) {  
    return a > b;  
} 

sort(vec.begin(),vec.end(),compare);
//lambda函数
sort(vec.begin(),vec.end(),[](int a,int b){return a>b;}

其实就是把自定义函数的类型和名称用[]代替
然后其他的都相同

不用sort函数怎么写呢
不用sort的话就是自己写一个排序算法吧 快速排序或者冒泡排序(这俩也不咋熟悉 冒泡好像是一次把一个最大的沉底 两层for循环 快速排序是选最左边的值 把比他大的放右边 比他小的放左边 用一个双指针实现 大概知道是啥 但是不熟悉写)
看carl思路代码随想录我那种原来是暴力算法
这个时间复杂度是 O(n + nlogn), 可以说是O(nlogn)的时间复杂度,但为了和下面双指针法算法时间复杂度有鲜明对比,我记为 O(n + nlog n)。
数组其实是有序的, 只不过负数平方之后可能成为最大数了。

那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。

此时可以考虑双指针法了,i指向起始位置,j指向终止位置。

定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置。

如果A[i] * A[i] < A[j] * A[j] 那么result[k–] = A[j] * A[j]; 。

如果A[i] * A[i] >= A[j] * A[j] 那么result[k–] = A[i] * A[i]; 。

喔喔喔 好神奇
区别在于题中给的条件是原来的数组就是有序的如果用sort的话原来的数组是否有序 sort都能变成升序的 没有利用到这个条件
第二次尝试

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        //因为是vector 所以觉得可以用sort
        //感觉也可以用一个单调队列 又有点麻烦
        /*for(int i=0;i<nums.size();i++)
        {
            int temp=nums[i];
            nums[i]=temp*temp;
        }
        sort(nums.begin(),nums.end());
        return nums;*/
        int leftIndex=0;
        int rightIndex=nums.size()-1;
        vector<int> n(nums.size(),0);
        int i=nums.size()-1;
        while(leftIndex<rightIndex&&i>=0){
            if(abs(nums[leftIndex])<abs(nums[rightIndex])){
                n[i]=nums[rightIndex]*nums[rightIndex];
                i--;
                rightIndex--;
            }
            else if(abs(nums[leftIndex])>abs(nums[rightIndex])){
                n[i]=nums[leftIndex]*nums[leftIndex];
                i--;
                leftIndex++;
            }
            else{
                n[i--]=nums[rightIndex]*nums[rightIndex];
                n[i--]=nums[leftIndex]*nums[leftIndex];
                rightIndex--;
                leftIndex++;
            }
        }
        if(leftIndex==rightIndex) n[0]=nums[leftIndex]*nums[leftIndex];
        return n;

    }
};

写了一大堆
vector n(nums.size(),0); 注意初始化方法
if(leftIndex==rightIndex) n[0]=nums[leftIndex]*nums[leftIndex];
这里是发现奇数数组会最后可能两者指到一起
ps: c++中没有平方函数
for()中第一项不能初始化两个 可以把初始化放在外面然后里面for(;巴拉巴拉;巴拉巴拉)啊 原来可以((〃>皿<))文心一言骗我for (int i = 0, j = A.size() - 1; i <= j;)
carl的比我的简略很多

class Solution {
public:
    vector<int> sortedSquares(vector<int>& A) {
        int k = A.size() - 1;
        vector<int> result(A.size(), 0);
        for (int i = 0, j = A.size() - 1; i <= j;) { // 注意这里要i <= j,因为最后要处理两个元素
            if (A[i] * A[i] < A[j] * A[j])  {
                result[k--] = A[j] * A[j];
                j--;
            }
            else {
                result[k--] = A[i] * A[i];
                i++;
            }
        }
        return result;
    }
};

哦哦哦 差别是不用单独考虑两个相等的情况
因为只有把这个平方数放到数组里之后 这个指针才会移动 所以不用担心会少保存某一边

209.长度最小的子数组

209.长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其总和大于等于 target 的长度最小的
子数组
[numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:

输入:target = 4, nums = [1,4,4]
输出:1
示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

提示:

1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105

第一次尝试
没理解题中的子数组 是要求连续的数

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        //直观上来看就是在一个排好序的数组中找到一部分加起来大于target 并且使这部分最短
        //该描述很像滑动窗口
        //但是我不是很熟练怎么实现
        int leftIndex=0;
        int rightIndex=-1;
        int sum=0;
        int n=INT_MAX;
        sort(nums.begin(),nums.end());
        if(nums[leftIndex]>=target) return 1;

        while(rightIndex<nums.size()-1){
            rightIndex++;
            sum+=nums[rightIndex];
            while(sum>=target){
                n=min((rightIndex-leftIndex+1),n);
                sum-=nums[leftIndex];
                leftIndex++;
                
            }
        }
        if(n==INT_MAX) return 0;
        return n;
        

    }
};

第二次尝试

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        //直观上来看就是在一个排好序的(错误的)数组中找到一部分加起来大于target 并且使这部分最短
        //该描述很像滑动窗口
        //但是我不是很熟练怎么实现
        int leftIndex=0;
        int rightIndex=-1;
        int sum=0;
        int n=INT_MAX;
        //sort(nums.begin(),nums.end());
        if(nums[leftIndex]>=target) return 1;

        while(rightIndex<nums.size()){
            if(rightIndex!=nums.size()-1){
                rightIndex++;
                sum+=nums[rightIndex];
            }
                
            while(sum>=target){
                n=min((rightIndex-leftIndex+1),n);
                sum-=nums[leftIndex];
                leftIndex++;
                
            }
        }
        if(n==INT_MAX) return 0;
        return n;
        

    }
};

也不对 在走到最后一个位置的时候没法判断 也没法终止
试着用文心一言

#include <vector>  
#include <climits>  
#include <algorithm> // 虽然在这个修正版本中不需要,但通常是个好习惯  
  
class Solution {  
public:  
    int minSubArrayLen(int target, std::vector<int>& nums) {  
        int leftIndex = 0;  
        int rightIndex = 0;  
        int sum = 0;  
        int n = INT_MAX;  
  
        // 无需排序  
  
        // 检查单个元素的情况  
        if (!nums.empty() && nums[0] >= target) return 1;  
  
        while (rightIndex < nums.size()) {  
            sum += nums[rightIndex];  
            while (sum >= target) {  
                n = std::min(n, rightIndex - leftIndex + 1);  
                sum -= nums[leftIndex];  
                leftIndex++;  
            }  
            rightIndex++;  
        }  
  
        // 如果没有找到满足条件的子数组  
        if (n == INT_MAX) return 0;  
        return n;  
    }  
};

好神奇
他把我的右指针的增加放到最后就实现了能判断最后一个数
并且右指针能从0开始判断了
但是这个检查单个元素应该写的不对 哦哦也对我没仔细看

 // 检查单个元素的情况  
        if (!nums.empty() && nums[0] >= target) return 1; 

实际测试中没有这句也行

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        //直观上来看就是在一个排好序的(错误的)数组中找到一部分加起来大于target 并且使这部分最短
        //该描述很像滑动窗口
        //但是我不是很熟练怎么实现
        int leftIndex=0;
        int rightIndex=0;
        int sum=0;
        int n=INT_MAX;
        //sort(nums.begin(),nums.end());
        //if(nums[leftIndex]>=target) return 1;

        while(rightIndex<nums.size()){
                
            sum+=nums[rightIndex];
            while(sum>=target){
                n=min((rightIndex-leftIndex+1),n);
                sum-=nums[leftIndex];
                leftIndex++;
                
            }
            rightIndex++;
        }
        if(n==INT_MAX) return 0;
        return n;
        

    }
};

代码随想录carl的
暴力解法是两个for循环
超时了

class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        int result = INT32_MAX; // 最终的结果
        int sum = 0; // 子序列的数值之和
        int subLength = 0; // 子序列的长度
        for (int i = 0; i < nums.size(); i++) { // 设置子序列起点为i
            sum = 0;
            for (int j = i; j < nums.size(); j++) { // 设置子序列终止位置为j
                sum += nums[j];
                if (sum >= s) { // 一旦发现子序列和超过了s,更新result
                    subLength = j - i + 1; // 取子序列的长度
                    result = result < subLength ? result : subLength;
                    break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break
                }
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }
};

滑动窗口
接下来就开始介绍数组操作中另一个重要的方法:滑动窗口。

所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果

在暴力解法中,是一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程。

那么滑动窗口如何用一个for循环来完成这个操作呢

首先要思考 如果用一个for循环,那么应该表示 滑动窗口的起始位置,还是终止位置。

如果只用一个for循环来表示 滑动窗口的起始位置,那么如何遍历剩下的终止位置?

此时难免再次陷入 暴力解法的怪圈。

所以 只用一个for循环,那么这个循环的索引,一定是表示 滑动窗口的终止位置

那么问题来了, 滑动窗口的起始位置如何移动呢?

这里还是以题目中的示例来举例,s=7, 数组是 2,3,1,2,4,3,来看一下查找的过程:(见链接里的图)

最后找到 4,3 是最短距离。

其实从动画中可以发现滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。

在本题中实现滑动窗口,主要确定如下三点

窗口内是什么?
如何移动窗口的起始位置?
如何移动窗口的结束位置?
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。

窗口的起始位置如何移动:如果当前窗口的值大于等于s了,窗口就要向前移动了(也就是该缩小了)。

窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

解题的关键在于 窗口的起始位置如何移动,如图所示:(见链接里的图)

while (sum >= s) {
                subLength = (j - i + 1); // 取子序列的长度
                result = result < subLength ? result : subLength;
                sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
            }

可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。

唔 和我的思路一样的 就是对于暴力解法的思路不清晰 导致在写滑动窗口如何结束的时候也有点晕(这个精髓部分我思路也是对的)

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;
    }
};

好吧 因为他用的是for 所以就是其实我的while难在了实现一个for的时候++的位置不对(小无语)

错误总结:

  • 以后滑动窗口用for,并且for的是结束位置

时间复杂度:O(n)
空间复杂度:O(1)
一些录友会疑惑为什么时间复杂度是O(n)。

不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。(大概理解一下)

59.螺旋矩阵II

59.螺旋矩阵II
给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。

在这里插入图片描述

示例 1:

输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
示例 2:

输入:n = 1
输出:[[1]]

提示:

1 <= n <= 20

看起来好难 之前做的有的是用数学技巧对折对折
这个我猜可能也是
但是也不知道 准备直接看答案 快到点回去了(急)
我有点不明白画出这个矩阵了之后怎么一行行把他输出出来 或者说怎么在画的同时把他填充进去 没啥思路
添加链接描述
(QAQ这题是咋和二分数组整上关系的)

大家还记得我们在这篇文章数组:每次遇到二分法,都是一看就会,一写就废 (opens new window)中讲解了二分法,提到如果要写出正确的二分法一定要坚持循环不变量原则。

求解本题依然是要坚持循环不变量原则

模拟顺时针画矩阵的过程:

填充上行从左到右
填充右列从上到下
填充下行从右到左
填充左列从下到上
由外向内一圈一圈这么画下去。

可以发现这里的边界条件非常多,在一个循环中,如此多的边界条件,如果不按照固定规则来遍历,那就是一进循环深似海,从此offer是路人。

这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。

那么我按照左闭右开的原则,来画一圈,大家看一下:
在这里插入图片描述
这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。

这也是坚持了每条边左闭右开的原则。

一些同学做这道题目之所以一直写不好,代码越写越乱。

就是因为在画每一条边的时候,一会左开右闭,一会左闭右闭,一会又来左闭右开,岂能不乱。

代码如下,已经详细注释了每一步的目的,可以看出while循环里判断的情况是很多的,代码里处理的原则也是统一的左闭右开。

整体C++代码如下:
(为啥画出来就能把他放到数组里了呢 在没看代码之前还是不明白)
(揭晓谜底(乌拉拉))

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
        int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
        int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
        int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
        int count = 1; // 用来给矩阵中每一个空格赋值
        int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位
        int i,j;
        while (loop --) {
            i = startx;
            j = starty;

            // 下面开始的四个for就是模拟转了一圈
            // 模拟填充上行从左到右(左闭右开)
            for (j; j < n - offset; j++) {
                res[i][j] = count++;
            }
            // 模拟填充右列从上到下(左闭右开)
            for (i; 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, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
            startx++;
            starty++;

            // offset 控制每一圈里每一条边遍历的长度
            offset += 1;
        }

        // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
        if (n % 2) {
            res[mid][mid] = count;
        }
        return res;
    }
};

时间复杂度 O(n^2): 模拟遍历二维矩阵的时间
空间复杂度 O(1)

定义二维数组的方式:

vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组

哦吼 原来因为你先创建了一个二维数组 然后你根据ij下标的位置就能精准定位你想填进去的位置了 所以在画的过程中就能把这个数组填满了 就是答案了
(卡住的点很微妙)
然后这里奇数数组的额外要加的辅助还挺多的 感觉挺难

for (j; j < n - offset; j++) 

这是啥写法 第一次见到 文心一言说

  • 第一个循环 for (j; j < n - offset; j++):
    这个循环假设 j 在循环之前已经被定义并可能已经被初始化了。如果 j 没有被初始化,那么这段代码的行为将是未定义的。
    循环的条件是 j < n - offset,并且在每次迭代结束时,j 会通过 j++ 增加 1
  • 第二个循环 for (; j < n - offset; j++):
    这个循环的初始化部分是空的(即没有执行任何初始化操作)。它同样假设 j 在这个 for 循环之前已经被定义并可能已经被初始化了。
    循环的条件和迭代表达式与第一个循环相同。

好神奇 没自己写一遍 但是看懂了(困了)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值