基础算法知识点------单调队列

前言:

啥为单调队列?
单调队列,顾名思义,是队列内元素保持一定单调性(单调递增或单调递减)的队列。这里的单调递增或递减是指的从队首到队尾单调递增或递减。既然是队列,就满足先进先出的特点。与之相对应的是单调栈。这里要说明一下,单调队列是可以变成单调栈的,只要保持队首永远不往前推进即可,所以下文只讲单调队列!

注:队首的数一般就是最值

单调队列(也称滑动窗口)套路
//三步走,掐头,去尾,入队
//我们可以用下标作为元素存入队列中,这样好判断窗口长度是否在范围内
int hh=0,tt=-1;
for(;;){
	if(hh<=tt && ...) { ... }   //判断窗口长度,去掉范围外的元素
	while(hh<=tt && ...){
		...
	}
	q[++tt] = ... ;
}
例题一:

给定一个大小为n≤106的数组。
有一个大小为k的滑动窗口,它从数组的最左边移动到最右边。
您只能在窗口中看到k个数字。
每次滑动窗口向右移动一个位置。
您的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。

输入格式
输入包含两行。
第一行包含两个整数n和k,分别代表数组长度和滑动窗口的长度。
第二行有n个整数,代表数组的具体数值。
同行数据之间用空格隔开。
输出格式
输出包含两个。
第一行输出,从左至右,每个位置滑动窗口中的最小值。
第二行输出,从左至右,每个位置滑动窗口中的最大值。

在这里插入图片描述

# include <iostream>
# include <algorithm>
# include <string>
using namespace std;
const int N = 1e6+10;
int a[N],q[N];
int main(void)
{
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=n;++i){
        cin>>a[i];
    }
    int hh=1,tt=0;
    for(int i=1;i<=n;++i){
        if(hh<=tt && i-k+1>q[hh]) ++hh;
        while(hh<=tt && a[i]<=a[q[tt]]) --tt;
        q[++tt] = i;
        if(i>=k) printf("%d ",a[q[hh]]);
    }
    //cout<<"*"<<endl;;
    cout<<endl;
    hh=1,tt=0;
    for(int i=1;i<=n;++i){
        if(hh<=tt && i-k+1>q[hh])  ++hh;
        while(hh<=tt && a[i]>=a[q[tt]]) --tt;
        q[++tt] = i;
        if(i>=k)  cout<<a[q[hh]]<<' ';
    }
    return 0;
}
例题二:

给定一个长度为N的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出-1。

输入格式
第一行包含整数N,表示数列长度。
第二行包含N个整数,表示整数数列。
输出格式
共一行,包含N个整数,其中第i个数表示第i个数的左边第一个比它小的数,如果不存在则输出-1。

数据范围
1≤N≤1e5
1≤数列中元素≤1e9

在这里插入图片描述

//这里我们可以看做是单调栈
# include <iostream>
# include <string>
# include <algorithm>
using namespace std;
const int N = 1e5+10;
int a[N],q[N];
int main(void)
{
    
    int n;
    cin>>n;
    for(int i=1;i<=n;++i)  cin>>a[i];
    int hh=1,tt=0;
    for(int i=1;i<=n;++i){
        while(hh<=tt && a[i]<=a[q[tt]]) --tt;
        if(hh<=tt)  cout<<a[q[tt]]<<' ';
        else  cout<<-1<<' ';
        q[++tt] = i;
    }
    return 0;
}
例题三:

上面两道题都是在一维的维度上进行单调队列,这一题是在二维上进行单调队列优化的一道算法题,同时也是一道面试题

题目:记一个矩阵所有元素中的最大值为这个矩阵的C值。给出一个nn的矩阵M,求所有kk(k<n)的子矩阵的C值之和。

例如下面这个5 * 5的矩阵,若k=3,那么图中绿色矩形框圈出的3*3子矩阵的C值为9,蓝色矩形框圈出的 3 * 3子矩阵的C值则为6,它的9个3 * 3的子矩阵的C值分别为9,9,8,7,7,6,6,6,6,其C值之和为64。

在这里插入图片描述
第一种做法是暴力做,不讲。

第二种做法如下:
如果我们先用单调队列,把每一列长度为k的滑动窗口的最大值先预处理出来,就像这样,先变成这么一个k*n的矩阵,然后再用一次单调队列,把每一行中长度为k的滑动窗口中的最大值求出来做一下累加,时间复杂度为O((n-k+1) * k)
在这里插入图片描述
也就是说,我们把子矩阵里的最大值浓缩成一个二维数组的一个元素,简单点来说,就是先对 行 进行区间k的合并,再进行 列 的区间k的合并。这样最后我们只要遍历这个二维数组,即可得到对应的k * k子矩阵的最大值之和

# include <iostream>
# include <algorithm>

using namespace std;
const int N = 5e3+10;
long long a[N][N];
int n,m,k;
int main(void)
{
	cin>>n>>m>>k;
	for(int i=1;i<=n;++i){   //打表数组
		for(int j=1;j<=m;++j){
			int t = __gcd(i,j);
			a[i][j] = (i*j)/t;
		}
	}
	

	
	
	for(int j=1;j<=m;++j){   /先合并行
		long long b[5010];
	    int hh=0,tt=-1;
		for(int i=1;i<=n;++i){
			if(hh<=tt && i-b[hh]+1 > k) ++hh;
			
			while(hh<=tt && a[b[tt]][j]<a[i][j]) --tt;
			
			b[++tt] = i;
			if(i>=k){
				//cout<<b[hh]<<' ';
				a[i-k+1][j] = a[b[hh]][j];
			//	++hh;
			}
	    }
	}
//	cout<<"&&&&&&&&&&&&&&&"<<endl;
//	for(int i=1;i<=n-k+1;++i){
//		for(int j=1;j<=m;++j){
//			cout<<a[i][j]<<' ';
//		}cout<<endl;
//	}
//	cout<<"&&&&&&&&&&&&&&&&&&&&&&"<<endl;
	for(int i=1;i<=n-k+1;++i){      //再合并列
		long long b[5010];
	    int hh=0,tt=-1;
		for(int j=1;j<=m;++j){
			if(hh<=tt && j-b[hh]+1 > k) ++hh;
			
			while(hh<=tt && a[i][b[tt]]<a[i][j]) --tt;
			
			b[++tt] = j;
			if(j>=k){
				//cout<<b[hh]<<' ';
				a[i][j-k+1] = a[i][b[hh]];
			//	++hh;
			}
		}
	}
	
//	for(int i=1;i<=n-k+1;++i){
//		for(int j=1;j<=m-k+1;++j){
//			cout<<a[i][j]<<' ';
//		}cout<<endl;
//	}
	
	long long ans = 0;
	//遍历二维数组,得到目标值
	for(int i=1;i<=n-k+1;++i){
		for(int j=1;j<=m-k+1;++j){
			ans+=a[i][j];
		}
	}
	cout<<ans<<endl;
	return 0;
 } 


单调栈例题

NO.1
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。
在这里插入图片描述
在这里插入图片描述
思路:首先我们枚举某一根柱子 i作为高h=heights[i];
随后我们需要进行向左右两边扩展,使得扩展到的柱子的高度均不小于 h。换句话说,我们需要找到左右两侧最近的高度小于 h的柱子,这样这两根柱子之间(不包括其本身)的所有柱子高度均不小于 h,并且就是 i 能够扩展到的最远范围。

暴力解法
//java
public class Solution {

    public int largestRectangleArea(int[] heights) {
        int len = heights.length;
        // 特判
        if (len == 0) {
            return 0;
        }

        int res = 0;
        for (int i = 0; i < len; i++) {

            // 找左边最后 1 个大于等于 heights[i] 的下标
            int left = i;
            int curHeight = heights[i];
            while (left > 0 && heights[left - 1] >= curHeight) {
                left--;
            }

            // 找右边最后 1 个大于等于 heights[i] 的索引
            int right = i;
            while (right < len - 1 && heights[right + 1] >= curHeight) {
                right++;
            }

            int width = right - left + 1;
            res = Math.max(res, width * curHeight);
        }
        return res;
    }
}

这时候我们就要用上单调栈的特性来解题

  1. 栈中存放了 j 值。从栈底到栈顶,j 的值严格单调递增,同时对应的高度值也严格单调递增;
  1. 当我们枚举到第 i 根柱子时,我们从栈顶不断地移除 height[j]≥height[i] 的 j 值。在移除完毕后,栈顶的 jj 值就一定满足 height[j]<height[i],此时 j 就是 i 左侧且最近的小于其高度的柱子。
  1. 这里会有一种特殊情况。如果我们移除了栈中所有的 j 值,那就说明 i 左侧所有柱子的高度都大于 height[i],那么我们可以认为 i 左侧且最近的小于其高度的柱子在位置 j=-1,它是一根「虚拟」的、高度无限低的柱子。这样的定义不会对我们的答案产生任何的影响,我们也称这根「虚拟」的柱子为「哨兵」。
    我们再将 i 放入栈顶。
单调栈
//C++
class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int n = heights.size();
        vector<int> left(n), right(n);
        
        stack<int> mono_stack;
        for (int i = 0; i < n; ++i) {
            while (!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) {
                mono_stack.pop();
            }
            left[i] = (mono_stack.empty() ? -1 : mono_stack.top());
            mono_stack.push(i);
        }

        mono_stack = stack<int>();
        for (int i = n - 1; i >= 0; --i) {
            while (!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) {
                mono_stack.pop();
            }
            right[i] = (mono_stack.empty() ? n : mono_stack.top());
            mono_stack.push(i);
        }
        
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            ans = max(ans, (right[i] - left[i] - 1) * heights[i]);
        }
        return ans;
    }
};

NO.2
给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
在这里插入图片描述

思路:我们可以将此题转换成上一题来做,看下图,你会发现这就是跟上一题的解法是一样的,只不过我们一层一层传递heights这个数组给之前的代码解题即可。
开个一维数组,记录每一列的情况,当出现某一个元素为0的情况时,应当把那一列的值改为0,因为要保证那一列必须是连续的1才行。
在这里插入图片描述

//C++
class Solution {
public:
    int maximalRectangle(vector<vector<char>>& matrix) {
        if(matrix.size()==0 || matrix[0].size()==0) return 0;

        int n = matrix[0].size();

        vector<int> height(n);
        for(int i=0;i<n;++i) height[i] = 0;

        int ans = 0;
        for(int i=0;i<matrix.size();++i){
            for(int j=0;j<n;++j){
                if(matrix[i][j]=='1')  height[j]++;
                else height[j]=0;
               // cout<<height[j]<<' ';
            }
          //  cout<<endl;
            ans = max(ans,largestRectangleArea(height));
        }
        return ans;
    }


    
    int largestRectangleArea(vector<int>& heights) {
        stack<int> left;
    stack<int> right;

    vector<int> q;
        for(int i=0;i<heights.size();++i){
            while(left.size() && heights[left.top()]>=heights[i]) left.pop();
            if(left.size()==0) q.push_back(-1);
            else q.push_back(left.top());

            left.push(i);
        }

        vector<int> p;
        for(int i=heights.size()-1;i>=0;--i){
            while(right.size() && heights[right.top()]>=heights[i]) right.pop();
            if(right.size()==0) p.push_back(heights.size());
            else p.push_back(right.top());

            right.push(i);
        }

        

        long long ans = 0;
        for(int i=0;i<heights.size();++i){
            int k = (p[heights.size()-i-1]-q[i]-1) * heights[i];
            if(ans<k) ans=k;
        }
        
        return ans;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值