滑动窗口(最大最小值)的经典例题

Posted 东条希尔薇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了滑动窗口(最大最小值)的经典例题相关的知识,希望对你有一定的参考价值。

滑动窗口简单概念

滑动窗口是我们假想出的一种数据结构,我们在这篇文章实现的窗口,能较快速的求区间最大最小值

在一些区间不回退的题目中运行效率也十分优秀

设窗口的左边界为l,右边界为r,(规定l<=r恒成立)

我们可以通过滑动右边界,从窗口的右边进入数字

也可以通过滑动左边界,从窗口左边出数字

滑动窗口求区间最大值(leetcode239)

原题链接

题目描述:

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。

思路求解

我们可以通过双端队列来实现我们求最大值窗口的结构,双端队列中存储数组中的下标

双端队列中必须始终维持根据下标对应的数字由大到小的顺序

不断的移动窗口,若进入的数会打破由大到小的平衡(即新数字大于队列末),队尾就不断出数字,直到满足双端队列的要求为止,再把当前数字插入

动图演示:

它可以表示在固定大小窗口中,只要窗口中包含这个数字,那么这个窗口的最大值永远队头

直到窗口中没有了这个最大值,那么把它从左边弹出

代码实现及注释:

class Solution 
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) 
        int n=nums.size();
        vector<int>ans;
        
        deque<int>qmax;//维持最大值的队列
        for(int r=0;r<n;r++)
        
            while(!qmax.empty()&&nums[qmax.back()]<=nums[r])
            
                qmax.pop_back();//这是维持队列由大到小的结构
                //将队尾所有比新数字小的数字全部弹出
            
            qmax.push_back(r);

            if(qmax.front()==r-k)
            
                qmax.pop_front();//这是最大值对应下标已经不在窗口中的情况
                //因为r-l=k
            

            if(r>=k-1)
            
                ans.push_back(nums[qmax.front()]);
                //窗口已经成型,就直接加入数字
            
        
        return ans;
    
;

求满足要求的子数组个数

题目描述:

给定一个数组arr和一个整数sum

若一个子数组(必须连接)满足子序列最大值-最小值<=sum,则称这个子数组符合要求

返回所有符合要求的子序列个数

思路求解:

我们可以先甩出两个结论:

  • 如果某个子数组满足要求,那么这个子数组的子数组都符合要求
  • 如果某个子数组不满足要求,那么以它为子数组的数组都不符合要求

为什么?

我们知道,我们最终要求的是max-min

如果满足要求了,那么max-min<sum是应该成立的

显然易见,它的子数组最大值只会比这个数组的最大值小,最小值只会比这个数组的最小值大,

所以这个情况下,max-min是只会减少的

不满足要求的同理可证

所以我们可以这样:

初始先把窗口放在最左边,维持窗口最大和最小值,不断更新窗口右边界,直到不满足要求

根据结论1,从l开始的所有子数组都满足要求**(l到l+1;l到l+2;l到l+3…等子数组)**

根据结论1,我们在更新l的时候,可以不用更新r

代码和注释:

	int AllLessNumSubArray(vector<int>& nums, int sum)
	
		int n = nums.size();
		deque<int>qmax;
		deque<int>qmin;
		int ans = 0;
		int r = 0;
		for (int l = 0; l < n; l++)
		
			while (r < n)
			
				while (!qmax.empty() && nums[qmax.back()] <= nums[r])
				
					qmax.pop_back();
				

				qmax.push_back(r);
				while (!qmin.empty() && nums[qmin.back()] >= nums[r])
				
					qmin.pop_back();
				
				qmin.push_back(r);
				//以上是维持最大最小队列
				//以下是滑动窗口

				if (nums[qmax.front()] - nums[qmin.front()] <= sum)
				
					r++;
				
				else
				
					break;
				

			

			ans += r - l;
			//更新过期元素
			if (qmax.front() == l)
			
				qmax.pop_front();
			

			if (qmin.front() == l)
			
				qmin.pop_front();
			
		
		return ans;
	

以上都是题目中明确要求求窗口最大最小值的,下面这道题就不太明显了

加油站最佳出发点

题目描述

给你两个数组,gas和cost

其实gas[i]表示第i号加油站的油量,cost[i]表示从i号到i+1号加油站消耗的油量

车初始油量为0

返回一个bool类型,表示第i号加油站出发,车能不能完整绕过一圈,只能逆时针绕圈

例如:

gas=[1,1,3,1]
cost=[2,2,1,1]

图如下:


如果你从0号加油站出发,cost[0]是2,但gas[0]=1,车不能到1号加油站,所以ans[0]返回false

总体返回:[0,0,1,0]

思路求解:

我们在解题前,可以先进行一个加工

先将gas和cost每个位置对应数字相减,得到处理后的数组nums

for (int i = 0; i < n; i++)

	nums[i] = gas[i] - cost[i];

这样就能表示出从某一站出发,能不能到达它的下一站,如果结果为负数,则不能到达

我们可以求出前缀和,如果中途出现了负数,表示这一圈不能够完成

例如:从0开始的前缀和,表示从0出发,中途途径每个加油站的剩余油量
从1开始的前缀和,表示从1出发,以此类推

因为我们要绕一圈,所以我们应该处理出两倍的原数组大小(不从0开始出发?)

		int m=n<<1;
		for (int i = 0; i < n; i++)
		
			nums[i] = gas[i] - cost[i];
			nums[i + n] = gas[i] - cost[i];
		

		for (int i = 1; i < m; i++)
		
			nums[i] += nums[i - 1];
		

我们本题的窗口要求大小与原数组相同,我们可以这样处理我们的数组:

维持最小值窗口

显而易见,如果最小值不是负数,那么窗口中就没有负数了

在更新数组窗口时,我们记得把窗口中的值减去窗口左边界前面一个值,这样就才是正确的从l开始的前缀和

直接判断当前最小值是否小于0即可

代码即注释:

	vector<bool> GasStation(vector<int>& gas, vector<int>& cost)
	
		int n = gas.size();
		int m = n << 1;
		vector<bool>ans(n, false);
		int* nums = new int[m];

		for (int i = 0; i < n; i++)
		
			nums[i] = gas[i] - cost[i];
			nums[i + n] = gas[i] - cost[i];
		

		for (int i = 1; i < m; i++)
		
			nums[i] += nums[i - 1];
		

		deque<int>w;
		
		//初始化窗口
		for (int i = 0; i < n; i++)
		
			while (!w.empty() && nums[w.back()] >= nums[i])
			
				w.pop_back();
			
			w.push_back(i);
		

		for (int offset = 0, i = 0, j = n; j < m; offset = nums[i++], j++)
		
			//offset表示窗口前面的数字,w始终维持窗口最小值
			//i左边界,j右边界

			if (nums[w.front()] - offset >= 0)
			
				ans[i] = true;
			

			if (w.front() == i)
			
				w.pop_front();
			

			while (!w.empty() && nums[w.back()] >= nums[j])
			
				w.pop_back();
			
			w.push_back(j);

		
		delete[]nums;
		return ans;
	

以上是关于滑动窗口(最大最小值)的经典例题的主要内容,如果未能解决你的问题,请参考以下文章

滑动窗口(最大最小值)的经典例题

滑动窗口经典例题之一——438. 找到字符串中所有字母异位词

2D 中的滑动窗口最小值/最大值

Acwing 154 滑动窗口(单调队列)经典模板

窗口的最大值与最小值更新结构(滑动窗口)

洛谷P1886滑动窗口