文章目录
- 算法概述
- 题目
- 下一个更大的元素 I
- 思路
- 代码
- 下一个更大元素 II
- 思路
- 代码
- 132 模式
- 思路
- 代码
- 接雨水
- 思路
算法概述
当题目出现 「找到最近一个比其大的元素」 的字眼时,自然会想到 「单调栈」 。——三叶姐
单调栈以严格递增or递减的规则将无序的数列进行选择性排序。
题目
下一个更大的元素 I
给你两个 没有重复元素 的数组 nums1
和 nums2
,其中 nums1
是 nums2
的子集。(题源力扣)
请你找出 nums1
中每个元素在 nums2
中的下一个比其大的值。
nums1
中数字 x
的下一个更大元素是指 x
在 nums2
中对应位置的右边的第一个比 x
大的元素。如果不存在,对应位置输出 -1
。
示例:
输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
- 对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。
- 对于 num1 中的数字 1 ,第二个数组中数字 1 右边的下一个较大数字是 3 。
- 对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
思路
- 倒序遍历
nums2
用stack
暂存严格递增的数据,即,若 栈不为空 且 当前遍历到的元素大于栈顶元素 则弹出栈顶元素。 - 用
unordred_map
存储 遍历到的元素(key
) 和 最近一个比其大的元素(value
) 之间的关系。经过步骤一后,若栈不为空,则栈顶元素即为value
,否则,value
为-1
。 - 正序遍历
nums1
并依赖unordred_map
得到题目要求的结果数组。
代码
class Solution {
public:vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {unordered_map<int, int> m;stack<int> st;for(int i = nums2.size()-1; i >= 0; i--){int t = nums2[i];while(st.size() && t>st.top()) st.pop();m[t] = st.empty() ? -1 : st.top();st.push(t);}vector<int> v;for(int i = 0; i < nums1.size(); i++){v.push_back(m[nums1[i]]);}return v;}
};
下一个更大元素 II
给定一个循环数组,输出每个元素的下一个更大元素。数字 x
的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数。如果不存在,则输出 -1
。(题源力扣)
示例:
输入: [1,2,1]
输出: [2,-1,2]
解释:
- 第一个 1 的下一个更大的数是 2;
- 数字 2 找不到下一个更大的数;
- 第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
思路
因为我们要循环搜索,因此要么将数组拉直——复制所有元素并添加在数组尾部;要么假设数组长度为真正长度的二倍,并且遍历的时候对长度取余,这里我们选择后一种方法。并将遍历分成正序遍历和逆序遍历两种方法:
正序遍历:
- 单调栈
st
用以记录下标,正序遍历给定的数组nums
,范围为[0, nums.size()-1]
,当前遍历到的下标值为i
,结果数组为v
,结果数组数值全部被初始化为-1
。 - 当 栈不为空 且 栈顶下标对应的元素 小于 当前遍历的元素 。
v[栈顶下标] = nums[i%n]
,栈顶下标对应的元素 的 下一个最大元素 即为 当前遍历到的元素。st.pop()
,弹出栈顶元素,单调栈遵循严格递增规则。
- 当前遍历到的元素其下标入栈,
st.push(i%n)
。由于每个元素都会被遍历到并且被压入栈中,而栈中每个元素都会被处理(没有下一个最大元素的栈中元素直接被弹出,反之则匹配下一个最大元素)。
逆序遍历:
- 单调栈
st
用以记录元素值,逆序遍历给定的数组nums
,范围为[nums.size()-1, 0]
,当前遍历到的下标值为i
,结果数组为v
,结果数组数值全部被初始化为-1
。 - 当 栈不为空 且 栈顶元素 小于等于 当前遍历的元素 。
st.pop()
,弹出栈顶元素,单调栈遵循严格递增规则。
- 当
i < nums.size()
时,开始逆序更新结果数组的元素,在此之前对单调栈进行的更新只为解决nums
最后一次降峰的元素(下例中的 5,4 )单次遍历无法匹配下一个更大元素的问题。以 2,3,6,4,5,4 为例:- 一次遍历只能得到结果数组 3,6,-1,5,-1,-1。对于 5,4 两个元素而言,无法正确匹配下一个更大元素,这也是和上一题的主要区别之所在。
- 因此倘若我们将整个遍历过程看作对
nums
的两次遍历,就会发现,第一次遍历结果是单调栈中只有 6,也就是解决了无法正确匹配5,4 两个元素的下一个更大元素,而第二次遍历则是对最后一次降峰之前所有元素进行匹配下一个最大元素的操作。
注:上面提到了降峰,这其实是对数组常见的称呼方法,意为将数组元素以折线图的形式表示看起来像一座座山峰,对于 2,3,6,4,5,4 而言,它们是形如下图的两座山峰:
代码
正序遍历:
class Solution {
public:vector<int> nextGreaterElements(vector<int>& nums) {int n = nums.size();vector<int> v(n, -1);stack<int> st;for(int i = 0; i < n*2; i++){int t = nums[i%n];while(st.size() && nums[st.top()]<t){v[st.top()] = t;st.pop();}st.push(i%n);//cout << nums[i%n] << " " << m[nums[i%n]] << endl;}return v;}
};
逆序遍历:
class Solution {
public:vector<int> nextGreaterElements(vector<int>& nums) {int n = nums.size();vector<int> v(n, -1);stack<int> st;for(int i = n*2-1; i >= 0; i--){int t = nums[i%n];while(st.size() && st.top()<=t) st.pop();if(i <= n-1) v[i] = st.empty() ? -1 : st.top();st.push(t);//cout << nums[i%n] << " " << m[nums[i%n]] << endl;}return v;}
};
132 模式
给你一个整数数组 nums
,数组中共有 n
个整数。132模式
的子序列 由三个整数 nums[i]
、nums[j]
和 nums[k]
组成,并同时满足:i < j < k
和 nums[i] < nums[k] < nums[j]
。(题源力扣)
如果 nums
中存在 132模式
的子序列 ,返回 true
;否则,返回 false
。
示例:
输入: nums = [1,2,3,4]
输出: false
解释: 序列中不存在 132 模式的子序列。
思路
- 单调栈
st
用以暂存可能为132模式
中2
的数据,遵循严格递减规则,变量sec
为真正的2
,将其初始化为INT_MIN
。 - 倒序遍历数组,因为我们目的在于枚举
2
,而2
必定从数组尾部优先出现,当前遍历到的下标为i
。- 当
nums[i] < sec
,确定了1
,返回true
。 - 当 栈不为空 且 当前遍历到的元素 大于 栈顶元素 ,当前元素暂定为
3
,更新sec
代表的2
。 - 优化:当前遍历到的元素 大于 sec ,当前元素入栈。本条规则的依据是:如果
nums[i] ≤ sec
,那么即使它在未来被弹出,也不会将sec
更新为更大的值。- 为什么 将
sec
更新为更大的值 如此重要呢?因为2
的值越大,那么我们之后找到1
的机会也就越大。
- 为什么 将
- 当
代码
class Solution {
public:bool find132pattern(vector<int>& nums) {int n = nums.size();stack<int> st; // 单调栈维护2st.push(nums[n-1]);int sec = INT_MIN; // second表示真正的2for(int i = n-2; i >= 0; i--){int t = nums[i];if(t < sec) return true; // 当前t作为1while(st.size() && t>st.top()){sec = st.top();st.pop();}if(t>sec) st.push(t); // 优化}return false;}
};
接雨水
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例:
输入: height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
解释: 上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
思路
单调栈 st
遵循严格递减规则,暂存可以作为 接水形状左边界的下标。用变量 res
保存能接多少雨水,正序遍历数组 height
:
- 当 栈不为空 且 当前遍历到的元素 大于 栈顶下标对应的元素,表明有可能出现可接雨水的 “容器” 。
- 将栈顶下标对应元素作为容器底部
bot
,然后弹出,将新的栈顶下标作为容器左边界left
。 - 容器能容纳的雨水为 长 * 高 =
i - left - 1 * (min(height[left], height[i]) - bot)
,解释一下,长度为右边界i
和 左边界left
的 差值减一,高度为 左边界高度和右边界高度中较小值 与 容器底部bot
的差值,毕竟木桶接水的多少取决于最短的一块木板。
- 将栈顶下标对应元素作为容器底部
- 其余情况则直接将 当前遍历到的元素下标 压入栈中,此时单调栈
st
遵循严格递减规则。
class Solution {
public:int trap(vector<int>& height) {int n = height.size();stack<int> st;int res = 0;for(int i = 0; i < n; i++){int t = height[i];while(st.size() && height[st.top()]<t){int bot = height[st.top()];//cout << "left: " << left << endl;st.pop();if(st.size()){int left = st.top();res += (i-left-1)*(min(t, height[left])-bot);}}st.push(i);}return res;}
};