关于单调栈的其他题目:
单调栈的用处是快速找到左右边第一个小于/大于栈顶元素的元素
【LeetCode:402 移掉k位数字】
【LeetCode:84. 柱状图中最大的矩形】
给定两个没有重复元素的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。找到 nums1 中每个元素在 nums2 中的下一个比其大的值。
nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出-1。
示例 1:
输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
对于num1中的数字4,你无法在第二个数组中找到下一个更大的数字,因此输出 -1。
对于num1中的数字1,第二个数组中数字1右边的下一个较大数字是 3。
对于num1中的数字2,第二个数组中没有下一个更大的数字,因此输出 -1。
示例 2:
输入: nums1 = [2,4], nums2 = [1,2,3,4].
输出: [3,-1]
解释:
对于num1中的数字2,第二个数组中的下一个较大数字是3。
对于num1中的数字4,第二个数组中没有下一个更大的数字,因此输出 -1。
注意:
nums1和nums2中所有元素是唯一的。
nums1和nums2 的数组大小都不超过1000。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/next-greater-element-i
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
直接对nums2中的每个元素,计算其右边第一个大于它的元素,存答案进哈希表,然后根据nums1的元素做key来读取哈希表即可
对于这个题,一般做法是枚举起始点,向右遍历找到第一个大于起点的值,这样的复杂度是O(n^2)
其实我们可以维护一个递减的单调栈,即栈顶的元素是最小的,然后开始遍历
后处理:
如果栈中元素有剩下,说明右边没有更大的,答案直接置-1
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2)
{
unordered_map<int, int> hash;
stack<int> s;
for(int i=0; i<nums2.size(); i++)
{
// 一直弹出尝试更新多个栈顶元素的答案
while(!s.empty() && s.top()<=nums2[i])
hash[s.top()]=nums2[i], s.pop();
s.push(nums2[i]); hash[nums2[i]]=-1;
}
// 后处理:右边没有更大的,答案置-1
while(!s.empty()) hash[s.top()]=-1, s.pop();
for(int i=0; i<nums1.size(); i++)
nums1[i] = hash[nums1[i]];
return nums1;
}
};
给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。
示例 1:
输入: [1,2,1]
输出: [2,-1,2]
解释:
第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数;
第二个 1 的下一个最大的数需要循环搜索,结果也是 2
注意: 输入数组的长度不会超过 10000。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/next-greater-element-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
寻找右边
和 下一个更大元素 I 一样,先是在右边找,维护单调栈并更新栈顶元素的答案
关键是循环,因为第一次找寻结束后,如果栈非空,说明栈中的元素都是右边没有比他更小的值了,这个时候应该在左边寻找第一个大于的元素了
寻找左边
从0开始,到最右边的下标之前(也就是栈顶元素的下标),重新跑一遍上面的更新,规则一样
后处理
如果两次循环都没有更新到答案,那么答案就是-1,这个后处理可以通过把答案数组初始化为-1来解决
不同点
因为答案需要元素一一对应,那么我们的栈存储的就应该是元素下标了,而不是元素,然后我们按照下标可以更新答案数组
代码
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums)
{
vector<int> ans(nums.size(), -1);
stack<int> s;
// 找右边
for(int i=0; i<nums.size(); i++)
{
while(!s.empty() && nums[s.top()]<nums[i])
ans[s.top()]=nums[i], s.pop();
s.push(i);
}
// 如果右边全都有比它小的,那么得到了答案
if(s.empty()) return ans;
// 如果仍然存在【在右边未找到比他小的元素】的元素,找左边
int stop = s.top();
for(int i=0; i!=stop; i++)
{
while(!s.empty() && nums[s.top()]<nums[i])
ans[s.top()]=nums[i], s.pop();
}
return ans;
}
};
相比于一般的枚举点作为最值,然后左右扩散,寻找边界,单调栈的引入,相当于反过来,枚举边界,然后看看有没有合适的最值,一个边界可以同时更新多个最值,就省略了重复的枚举
栈的单调性保证了左边的单调,更新答案必当发生在新元素与栈顶及其左边的单调方向不一致时,两边保持相反方向的单调,那么就保证了栈顶元素的最值性
单调栈擅长快速地找到
对于上面两个题,都是单边找第一个大值
双边缘
对于某些题目,比如一个区间的答案和【区间最值,区间长度】有关,那么应该是单调栈了
快速找左右第一个大于x的值,可以确定x是某个区间的极小值(极大值同理),然后更新答案
比如一个区间的得分是区间长度*区间最大值,那么就要通过单调栈快速找到两个边界,然后更新答案
以上。。