【单调栈】

单调栈总结

摘自单调栈1
摘自单调栈2


1、定义

单调栈是一种特殊的栈,其栈内的元素都保持一个单调性(单调递增或者递减)。

  • 单调递增栈,从栈底到栈顶依次递增(单调非递减栈:允许有相等)
  • 单调递减栈,从栈底到栈顶依次递减(单调非递增栈:允许有相等)

假设下图是一个栈内元素的排列情况(单调递增的栈):
这里写图片描述

2、作用

利用单调栈,可以找到从左(或者右)遍历第一个比它小(或者大)的元素的位置。
也可以说是 求某组数以其中某一个数字为最小值的最大延伸区间
比如 {2 3 2 5 1 4},以第一个2为最小值可以延伸的区间为{2, 3, 2, 5}

3、算法过程

假设有一个单调递增的栈 S和一组数列: a = { 5 3 7 4}
用数组L[i]表示 第i个数向左遍历的第一个比它小的元素的位置

如何求L[i]?

3.1 朴素的算法 O(n^2)

可以按顺序枚举每一个数,然后再依此向左遍历。 但是当数列单调递减时,复杂度是严格的O(n^2)。

3.2 单调栈 O(n)

我们按顺序遍历数组(i : 1 -> n),然后构造一个单调递增栈。栈中存放的是元素下标,而非元素本身。

        {5 3 7 4}

(1)i = 1时,因为栈为空,L[1] = 0,此时再将第一个元素的位置下标1存入栈中。
此时栈中情况:这里写图片描述

(2)i = 2时,因当前元素a[i] = 3小于栈顶元素下标1对应的元素a[1] = 5,故将下标1弹出栈, 此时栈为空 ,故L[2] = 0 。然后将元素3对应的位置下标2存入栈中。
此时栈中情况:这里写图片描述

(3)i = 3时,因当前元素a[i] = 7大于栈顶元素下标2对应的元素a[2] = 3,故
L[3] = S.top() = 2 (栈顶元素的值,说明第一个比它小的元素的下标为多少),然后将元素7对应的下标3存入栈 。
此时栈中情况:这里写图片描述

(4)i = 4时,因当前元素a[i] =4小于栈顶元素下标3对应的元素a[3] = 7,为保持单调递增的性质,应将栈顶元素下标3弹出 ,而当前元素a[i] =4大于弹出元素后的栈顶元素下标2对应的元素a[2] = 3,不需要再继续弹出, 此时 L[4] = S.top() = 2;然后将元素4对应的下标4存入栈。
此时栈中情况:这里写图片描述

(5)至此 算法结束

对应的结果:
a : 5 3 7 4
L : 0 0 2 2

(6)总结
一个元素向左遍历的第一个比它小的数的位置就是将它插入单调栈时栈顶元素的值,若栈为空,则说明不存在这么一个数。然后将此元素的下标存入栈,就能类似迭代般地求解后面的元素

4、模板

stack<int> s;
for(int i = 1; i <= n; ++i)
{
    while(s.size() && a[s.top()] >= a[i]) s.pop();
    if(s.empty()) l[i] = 0;
    else l[i] = s.top();
    s.push(i);
}

5、应用

1.给定一组数,针对每个数,寻找它和它左边第一个比它小的数之间有多少个数。

2.给定一序列,寻找某一子序列,使得子序列中的最小值乘以子序列的长度最大。

3.给定一序列,寻找某一子序列,使得子序列中的最小值乘以子序列所有元素和最大。

6、类型一例题

1.POJ 3250

题意:有一群牛站成一排,每头牛都是面朝右的,每头牛可以看到他右边身高比他小的牛。给出每头牛的身高,要求每头牛能看到的牛的总数。

思路:这也就是应用1所说的求每个数和它右边第一个比它大的数之间的数的个数,分别求出后相加即可。朴素的做法是双重循环遍历,时间复杂度为O(n^2),用单调栈为O(n)。

题解:POJ 3250题解

7、类型二例题

1.POJ 2559
POJ 2559 &&HDU 1506 && 51NOD 1102 类似
题意:有N个矩形,宽度都为1,给出N个矩形的高度,求由这N个矩形组成的图形包含的最大的矩形面积。

思路:可以转化为求区间最小值乘以区间长度的最大值。普通的思路是两重循环遍历,时间复杂度为O(n^2),用单调栈为O(n)。

题解:POJ 2559 题解
解法一
解法二
解法三

2.POJ 3494

题意:求仅由0,1组成的矩阵中,全部由1组成的小矩阵的最大面积。

思路:这个是上一题POJ 2559的升级版,把一维的操作变成二维的即可。这之前需要一个预处理。

题解:POJ 3494题解

8、类型三例题

1.POJ 2796

题意:给出一个序列,求出一个子序列,使得这个序列中的最小值乘以这个序列的和的值最大。

思路:直接用单调栈解决即可,由于维护单调栈的过程中会改变原数组的值,所以需要加一个sum数组保存前缀和,也方便计算区间元素和。

题解:POJ 2796题解

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
单调是一种常用的数据结构,用于解决一类特定的问题,其中最常见的问题是找到数组中每个元素的下一个更大或更小的元素。在Codeforces编程竞赛中,单调经常被用于解决一些与数组相关的问题。 下面是单调的一般思路: 1. 创建一个空。 2. 从左到右遍历数组元素。 3. 对于每个元素,将其与顶元素进行比较。 - 如果当前元素小于等于顶元素,则将当前元素入。 - 如果当前元素大于顶元素,则将顶元素弹出,并将当前元素入。 4. 重复步骤3,直到遍历完所有元素。 这样,最后剩下的中元素就是没有下一个更大或更小元素的元素。在使用单调求解具体问题时,我们可以根据需要进行一些特定的操作。 例如,如果要找到一个数组中每个元素的下一个更大的元素,可以使用单调递减。具体操作如下: 1. 创建一个空和一个空结果数组。 2. 从左到右遍历数组元素。 3. 对于每个元素,将其与顶元素进行比较。 - 如果当前元素小于等于顶元素,则将当前元素入。 - 如果当前元素大于顶元素,则将顶元素弹出,并将其在结果数组中的位置记录为当前元素的下一个更大元素的索引。 4. 将当前元素入。 5. 重复步骤3和4,直到遍历完所有元素。 6. 结果数组中没有下一个更大元素的位置,可以设置为-1。 以下是一个使用单调递减求解下一个更大元素问题的示例代码: ```cpp #include <iostream> #include <stack> #include <vector> std::vector<int> nextGreaterElement(std::vector<int>& nums) { int n = nums.size(); std::vector<int> result(n, -1); std::stack<int> stack; for (int i = 0; i < n; i++) { while (!stack.empty() && nums[i] > nums[stack.top()]) { result[stack.top()] = i; stack.pop(); } stack.push(i); } return result; } int main() { std::vector<int> nums = {1,3, 2, 4, 5, 1}; std::vector<int> result = nextGreaterElement(nums); for (int i = 0; i < result.size(); i++) { std::cout << "Next greater element for " << nums[i] << ": "; if (result[i] != -1) { std::cout << nums[result[i]]; } else { std::cout << "None"; } std::cout << std::endl; } return 0; } ``` 以上代码将输出: ``` Next greater element for 1: 3 Next greater element for 3: 4 Next greater element for 2: 4 Next greater element for 4: 5 Next greater element for 5: None Next greater element for 1: None ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值