优选算法——滑动窗口1(单调性)

🔥近津薪荼:个人主页

🎬个人专栏:《c语言基础知识详解》《c++基础知识详解》

**✨**自信人生二百年,会当水击三千里。


本期知识点导图

1.上期参考代码:

cpp 复制代码
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        sort(nums.begin(),nums.end());
        int n=nums.size();
        vector<vector<int>> ret{};
        for(int i=0;i<n;)
        {
            for(int j=i+1;j<n;)
            {
                int left=j+1;
                int right=n-1;
                long long aim=(long long)target-nums[i]-nums[j];//这里要防溢出
                while(left<right)
                {
                    int sum=nums[left]+nums[right];
                    if(sum<aim)
                    left++;
                    else if(sum>aim)
                    right--;
                    else
                    {
                        ret.push_back({nums[i],nums[j],nums[left++],nums[right--]});
                        while(left<right&&nums[left]==nums[left-1])left++;//双指针去重
                        while(left<right&&nums[right]==nums[right+1])right--;
                    }
                }
                 j++;
                while(j<n&&nums[j-1]==nums[j])j++;//对j去重
               
            }i++;
            while(i<n&&nums[i-1]==nums[i])i++;//对i去重
        }
        return ret;
    }
};

这里注意在算aim时,可能会发生数据溢出

because:

在目前主流编译器中:

类型 标准最小位数 取值范围 十进制
int 16 位 -2^31 ~ 2^31-1 -2.14*10^9 ~ 2.14*10^9
long long 64 位 -2^63 ~ 2^63-1 -9.22*10^18~9.22*10^18

取极端情况,显然会导致数据溢出

这你说阴不阴

还有要注意,去重的时候,防越界的判断条件要放到前边,当不满足条件时,直接短路 ,否则在执行第二个逻辑判断的时候,会造成访问越界哦。

2.题目解析

本期要讲解的题目是

长度最小的子数组(点击跳转)

重点:

  • 整数数组
  • 要找满足条件的(和>target)最小的连续的子数组
  • 找到了返回子数组长度,找不到返回0

3.解题思路

3.1暴力解法

整体思路是穷举所有子数组,找到最小符合条件的子数组,并返回最小长度

cpp 复制代码
class Solution {
public:
    int xxj(int target, vector<int>& nums) {
        int n=nums.size();
        int length=n+1;//设置一个不可能达到的长度
        for(int i=0;i<n;i++)
        {
     
            for(int j=i;j<n;j++)
            {
                int sum=0;//每次进来先刷新sum
                int k=i;
                while(k<=j)//累加i到j区间里面的值
                {
                    sum+=nums[k++];
                }
                if(sum>=target)//取最小值
                {
                    int temp=j-i+1;
                    length=min(temp,length);
                }
            }

        }
        return length==n+1?0:length;
    }
};

此算法的时间复杂度为O(N^3),必然超时。

3.2优化算法

模拟暴力算法

我们先来模拟一下暴力解法,在此基础上探索更加优秀的算法。

我们暴力解法的整体框架是套三层循环:

第一层固定子数组的左区间

第二层枚举子数组的右区间

第三层对区间里的元素求和

我们用题给示例模拟一下:

定义一对双指针 ,模拟前两层的穷举工作,并尝试寻找优解

left固定左区间,right向右遍历枚举右区间。遍历的过程中对区间里面的元素求和。

当right遍历到2时,得到符合要求的子数组:

两个优化点:

刷新length之后,right继续向后遍历,我们将会发现,后边的子数组都必然满足条件 ,这是因为,数组里的元素全是正数,求和只会越来越大**(单调性),** 但是区间长度会越来越长,我们要找的是最小子数组,所以当right遍历到符合要求的右区间之后,就不必要继续向后遍历了。此为优化点1。(此时就可以用双指针算法来代替循环了)

这时我们让left++,固定新的左区间,枚举右区间,重复前边的逻辑,这边要注意的是,求新的区间的元素和,不必让right返回到left处重新开始遍历区间内元素求和,我们只需要把退出区间的元素,也就是之前的left指向的元素减去即可,当然此次left++之后,也要进行逻辑判断 看此时的子数组使用都依然满足条件,也就是说要进行循环此为优化点2。(规避了第三层的求和循环)

时间复杂度分析

经过此两次优化,我们发现,使用双指针似乎还是要嵌套两层循环(right向右遍历&逻辑判断)才能解决问题,时间复杂度似乎是O(N^2) ,但是我们的暴力算法也可以通过优化点2来规避第三层循环,时间复杂度也是**O(N^2)**啊,使用双指针的优势在哪里?

我们判断时间复杂度不能只看嵌套的循环数,我们要看实际上执行了多少条代码,我们使用优化之后的暴力解法,前两个循环必然是实打实地执行了0.5*n*(n-1)次 内循环的语句(信息维护和逻辑判断的语句),每次内循环都要重头开始遍历,但是我们使用双指针的话,我们遍历过程中,在每次进行完判断之后,一次只移动一个指针,right指针至多移动n次(只往右走 ),left指针至多移动n次(只往右走 ),也就是说,至多也就执行n+n次 信息维护和逻辑判断的语句。他们用大O渐进表示法分别就是,O(N^2)和O(N)。

4.滑动窗口

4.1是什么

说白了,就是**"同向双指针"**

什么是双指针,应该不用我多说了,双指针一个指向窗口的左端点,一个指向窗口的右端点,两端点及其中间的内容所包含的元素就是我们要维护的窗口信息

什么是同向,就是都只向同一个方向移动,在本题中的left和right的移动方式就是同向。

为什么叫滑动双指针呢,当同向双指针在数组中移动的时候,就像一个"移动"的窗口一样,窗口里面的内容不断地在改变(进出窗口),我们看到的(维护的)信息就不断在改变,就像我们坐高铁的时候,透过窗口向外看,看到的风景不断从窗口的一端进来,从另一端出去一样。

能想出这种比喻,我也是天才

4.2什么时候用

像本题,当窗口中的需维护的信息单调性的时候(比如我们这里的求子数组的元素和,right向右移动(元素进窗口),和成单调增的情形),我们够可以考虑使用滑动窗口来解决问题。

本质上是造成双指针的同向移动

4.3怎么用

  1. 初始化两个同向双指针充当窗口的左右端点。
  2. 元素进窗口(right++),维护信息(sum)
  3. 判断是否要出窗口 (left++)

在滑动窗口的时候(第二步和第三步)我们要更新结果(length),当然更新结果是要就题论题的,可能是在进窗口之后出窗口之前,也可能是出窗口之后,本题是在信息维护的时候,也就是进窗口之后,出窗口之前更新结果。

5.小结&预告

5.1小结:

本期在模拟穷举算法的过程中一步步推导,通过两个优化点,引出滑动窗口的概念(即同向双指针)。并介绍了滑动窗口的用法。这是滑动窗口专题的第一个算法,大家要重点掌握"窗口"思维,最好自己画画图 ,模拟一下滑动窗口的过程,并尝试代码的实现

参考代码见下期博客

5.2预告:

下期要讲解的题目是:

无重复字符的最长子串

加油!

相关推荐
头发还没掉光光2 小时前
Linux 高级 IO 深度解析:从 IO 本质到 epoll全面讲解
linux·服务器·c语言·c++
爱装代码的小瓶子2 小时前
【C++与Linux基础】进程如何打开磁盘文件:从open()到文件描述符的奇妙旅程(更多源码讲解)
linux·开发语言·c++
diediedei2 小时前
嵌入式C++驱动开发
开发语言·c++·算法
80530单词突击赢2 小时前
C++容器对比:map与unordered_map全解析
c++
燃于AC之乐2 小时前
《算法实战笔记》第10期:六大算法实战——枚举、贪心、并查集、Kruskal、双指针、区间DP
算法·贪心算法·图论·双指针·区间dp·二进制枚举
田野追逐星光2 小时前
STL中容器list -- 讲解超详细
开发语言·c++·list
diediedei2 小时前
高性能计算通信库
开发语言·c++·算法
齐鲁九零2 小时前
【AI推荐】推荐适合学英语的美剧
学习
蒸蒸yyyyzwd2 小时前
算法学习笔记
笔记·算法