本人对单调栈
印象深刻,前几周去字节跳动实习,就问了这类题,由于没写出来,错失了实习的机会。。
顾名思义,单调栈实际上就是栈,只是利用了一些巧妙的逻辑,使得每次新元素入栈后,栈内的元素都保持有序(单调递增或单调递减)
首先看一个问题,没错,就是字节面试原题!!!
给你一个数组[2,1,2,4,3]
,返回数组[4,2,4,-1,-1]
意思就是返回原数组每个元素后第一个比它大的元素,如果没有比它大的元素,就返回-1
。比如数组第一个元素2
后第一个比它大的元素是4
,第二个元素1后第一个比它的元素是2
,而4
后没有比它大的元素,因此返回-1
暴力方法很好解决,但是时间复杂度为 O ( n 2 ) O(n^2) O(n2)
因此用下面单调栈
的方法来解决,首先看下代码
vector<int> nextGreaterElement(vector<int>& nums){
vector<int> ans(nums.size());
stack<int> s;
for(int i = nums.size() - 1; i >= 0; i--){
while(!s.empty() && s.top() <= nums[i]){
s.pop();
}
ans[i] = s.empty() ? -1 : s.top();
s.push(nums[i]);
}
return ans;
}
这就是单调栈解决问题的模板,for循环从后往前扫描元素,如果栈中的元素比当前元素小,那么就出栈,因为当前元素把栈顶元素挡住了,因此,栈中比当前元素小的元素也没有存在的必要了
从整体看,总共有n个元素,每个元素都被push入栈了一次,而最多会被pop一次,没有任何冗余操作,所以时间复杂度为 O ( n ) O(n) O(n)
下面来个简单的变形加深一下理解
一个数组T=[73,74,75,71,69,72,76,73]
,数组存放的是近几天的气温,求对于每一天,至少还要等多少天才能等到一个更暖和的气温,如果等不到返回0
例如,给定数组T=[73,74,75,71,69,72,76,73]
,返回[1,1,4,2,1,1,0,0]
其实这个问题本质还是找Next Greater Number
,只需要原本栈中的元素换为数组的索引即可
vector<int> getGreaterElements(vector<int>& nums){
int n = nums.size();
vector<int> res(n);
stack<int> s;
for(int i = 2*n-1; i >=0; i--){
while(!s.empty() && s.top() <= nums[i%n])
s.pop();
res[i%n] = s.empty() ? -1 : s.top();
s.push(nums[i%n]);
}
return res;
}
还有一个问题,如果数组是环形的怎么办呢?
给一个数组[2,1,2,4,3]
,返回数组[4,2,4,-1,4]
。因为数组是环形的,所以最有一个元素3绕了一圈后找到了比自己大的元素4
我们一般通过%运算符来模拟环形数组的效果,在这里可以考虑这样的思路,将原始数组翻倍
,就是将[2,1,2,4,3]
变为[2,1,2,4,3,2,1,2,4,3]
,这样的话,每个元素不仅可以比较自己右边的元素,而且也可以和左边的元素比较
在这里,我们不必将这个翻倍
的数组构造出来,而是利用循环数组的技巧来模拟,看代码
vector<int> getGreaterElements(vector<int>& nums){
int n = nums.size();
vector<int> res(n);
stack<int> s;
for(int i = 2*n-1; i >=0; i--){
while(!s.empty() && s.top() <= nums[i%n])
s.pop();
res[i%n] = s.empty() ? -1 : s.top();
s.push(nums[i%n]);
}
return res;
}