【算法基础】滑动窗口+单调队列

本文介绍了滑动窗口算法在解决数组问题中的应用,包括求满足特定性质的子数组长度、最大连续1的个数和将元素减到0的最小操作数。同时,涉及了单调队列的概念及其在滑动窗口最大值问题中的运用。最后提到了一些力扣题单作为练习资源。

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

主要解决的问题

数组里的每个子数组的性质

  1. 通常说给定一数组nums,让我们求一段子数组所满足的性质,如nums中最短之和大于target的子数组长度。

数组里的每个子数组里面元素的性质

  1. 求给定一子数组长度,让我们求子数组中元素的最值,还有就是求其各子数组之和或者积。

这类问题往往可以借助前缀和,只要确认前缀数组的长度就行,下面几个题目都十分经典,没有明白尽可能要弄明白或者看看讲解视频


1.滑动窗口模板和步骤(第一类问题)

1.1 模板+长度最小数组例题

上来如果直接说模板,感觉有点太唐突了,先结合一个简单例题体会一下。
问题是求最短的子数组长度并满足要大于等于target,也就是我们的第一类问题。
在这里插入图片描述

思路: 使用两个下标一前一后,不断累加,刚好大于等于target时候,就记录下来 R - L + 1的长度,来每次比较这个长度求最小。下面看代码。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int sum = 0,minlen  = 2e9;
        // l 和 r 类似双指针一样,一前一后
        for(int l = 0,r = 0;r<nums.size();r++ ){
            //进窗口,累加进窗口
            sum += nums[r];
            //判断条件
            while(sum>=target){
                //记录更新答案
                minlen = min(minlen,r-l+1);
                //出窗口
                sum -= nums[l++]; 
            }
        }
        //返回结果
        return minlen == 2e9?0:minlen;
    }
};

步骤:

1.使用两个下标来维护一段数组区间
2.开始进窗口,得到问题所满足的
3.进窗口时候,判断是否条件已经满足,满足了更新结果
4.更新结果之后,要及时出窗口
5.一些细节,比如刚开始就条件不满足,要判空等等。
在这里插入图片描述


1.2 条件+最大连续1的个数

这问题和上面问题其实道理一样,加了一些判断条件,难题就是简单题加条件出来的。我们只要好好记录最初的模板,然后中等和难题分析其中的条件就行。
在这里插入图片描述
思路: 和第一题类似,我们只要判断一下是0的情况,加一个计数就行,统计0的数目。

class Solution {
public:
int longestOnes(vector<int>& nums, int k) {
    int l = 0,r = 0,maxlen = 0,n = nums.size();
    //模拟一个队列 h队头,t队尾
    int h = 0,t = 0;
    int mk[100000]={0};
    while (r < n) {
            //等于0的情况
            if (nums[r] == 0){
                //填加到队列里面
                mk[t++] = r;
                //队列里面的0数量大于了k
                if (t-h > k) {
                    // +1是跳到第一个0的后一个位置,
                    l = mk[h] + 1;
                    //把一个0出队
                    h++;
                }
            }
        maxlen = max(maxlen,r-l+1);
        ++r;
    } 
      return maxlen;
    }
};

如果你做完两道题目,肯定对滑动窗口有一些感觉,模板就是这样吗。变得只是不同题目的限制条件。熟悉了基本模板套路就行,不要说我一定要会所有的滑动窗口题目,或者一类,只要会6,7成这类算法就可以过了。


1.3 思考+将x减到0的最小操作数

我这里默认你已经通过了前面两个题目?下面我们看看下面这个
在这里插入图片描述
思路: 也就是说题目让我们求左右两边数组元素之和等于X,还是最短的。我们反过来求中间数组元素之和等于 SUM - X,且最长的就行了。其他步骤和第一题一模一样,这题通过率只要39%,所以模板题一定要掌握。

class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
        //nums所有元素和
        int sum = 0;
        for(auto k:nums) sum+=k;
        if(sum<x) return -1;    //sum<x,直接返回
        //if(sum==x) return nums.size();
        int maxlen = -1;
        //在数组中间寻找值为sum-x的最长子数组
        for(int l = 0,r = 0,tmp = 0;r<nums.size();r++){
            tmp += nums[r];
            while(tmp > sum - x ){
                tmp -= nums[l++];
            }
            if(tmp == sum -x) maxlen = max(maxlen,r-l+1);
        }
        return maxlen==-1?-1:nums.size()-maxlen;
    }
};

2.单调队列模板和步骤(第二类问题)

也关于从子数组的元素性质的一些问题。

2.1 单调队列概念

初步简单认识
1.单调队列是一个数据结构,并不是STL里面的内容。

2.单调队列为何说单调,因为是队列中的元素始终保持着单增或者单减的特性。(注意始终保持这四个字)

3.单调队列不只是做到了排序,还可以实现一个功能:在每次加入或者删除元素时都保持序列里的元素有序,即队首元素始终是最小值或者最大值,这个功能非常重要,单调队列我们就是使用的这个功能。

2.2 滑动窗口最大值

也没有很难,有的难题没有中等题目难得,大家理清思路就行。
在这里插入图片描述

思路: 具体步骤就是保存每次加入元素,保持队列元素递增或者递减,如果不满足就不加入。窗口满了就出元素。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n = nums.size();
        deque<int> q;
        //还没有形成窗口
        //使用第一个窗口去初始化单调队列
        for (int i = 0; i < k; ++i) {
            while (!q.empty() && nums[i] >= nums[q.back()]) {
                q.pop_back();
            }
            q.push_back(i);//队列存储的是下标
        }
        //ans存储每个窗口的最大值
        vector<int> ans = {nums[q.front()]};
        
        //形成了窗口
        for (int i = k; i < n; ++i) {
            while (!q.empty() && nums[i] >= nums[q.back()]) {
                q.pop_back();
            }
            q.push_back(i);
            //检查最大元素是否在窗口区间里
            if (q.front() <= i - k) {
                q.pop_front();
            }
            ans.push_back(nums[q.front()]);
        }
        return ans;
    }
};


3.滑动窗口力扣题单

下面总结了一些关于滑动窗口得经典题目,感兴趣得可以做做。

  1. 904.水果成篮
  2. 438.找到字符串中所有字⺟异位词
  3. 3.无重复得最长子串
  4. 30.串联所有单词得子串
  5. 76.最小覆盖子串
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值