单调栈:一种数据结构。
-
定义:栈中存在的元素是单调递增或单调递减的。
-
本质:空间换时间。
2.1 单调递增栈:栈中的元素 --从栈底到栈顶是递增的。(也可反过来定义)
2.2 单调递减栈:栈中的元素 --从栈底到栈顶是递减的。 -
单调栈的作用:存放之前遍历过的元素,用来和当前的元素进行对比。
-
使用单调栈的目的:寻找数组中每个元素的左边或右边第一个更大或更小元素。
-
三个判断条件。
(1)当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
(2)当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
(3)当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况
下面通过题目来进行理解:
给你一个数组 prices ,其中 prices[i] 是商店里第 i 件商品的价格。
商店里正在进行促销活动,如果你要买第 i 件商品,那么你可以得到与 prices[j] 相等的折扣,其中 j 是满足 j > i 且 prices[j] <= prices[i] 的 最小下标 ,如果没有满足条件的 j ,你将没有任何折扣。
请你返回一个数组,数组中第 i 个元素是折扣后你购买商品 i 最终需要支付的价格。
非常经典的一道单调栈的题,题目的要求就是求数组中每个元素右边的第一个较小元素,符合单调栈的定义,可以用单调栈来解决,当然,直接用二重循环也可以,但是要耗费更多的时间。
第一步----定义栈和辅助数组:
stack<int>stk;
vector<int>ans(prices.begin(),prices.end());//define the answer array
这里用C++里的STL来定义一个栈,放int类型的数据。
定义一个ans数组,用来存储结果。
其中,栈里放元素下标,以便可以在后续更新值的时候进行对应。
ans数组直接用prices数组初始化,方便后续值不变时直接保留,省去后续的遍历赋值操作。
第二步----遍历数组,进行单调栈操作
for(int i=0;i<n;i++){
if(!stk.empty() && prices[i]>prices[stk.top()]) stk.push(i);
else{
while(!stk.empty() && prices[i]<=prices[stk.top()]){
ans[stk.top()]=prices[stk.top()]-prices[i];//:=
stk.pop();
}
stk.push(i);
}
}
本题求右边第一个小于对于它的元素,所以只能分到两种情况:
(1)prices[i]<=
prices[st.top()]:
- 此时我们找到了符合条件的元素,应当进行ans数组的更新(&出栈操作),然后循环判断是否还有符合条件的元素。所以上面用了while循环,注意细节!
(2)prices[i]>
prices[st.top()]:
- 此时为一般情况,直接进行入栈操作
当然,我们发现无论是否找到了合适的元素,在每个方面判断结束后都要进行入栈操作,所有可以将入栈操作合到一起,即:
for(int i=0;i<n;i++){
while(!stk.empty() && prices[i]<=prices[stk.top()]){
ans[stk.top()]=prices[stk.top()]-prices[i];//:=
stk.pop();
}
stk.push(i);//small ,equal,and big
}
ok,单调栈就是这样,是的,你没看错,就是这么简单! 完整的代码如下:
class Solution {
public:
vector<int> finalPrices(vector<int>& prices) {
int n=prices.size();
stack<int>stk;
vector<int>ans(prices.begin(),prices.end());//define the answer array
for(int i=0;i<n;i++){
while(!stk.empty() && prices[i]<=prices[stk.top()]){
ans[stk.top()]=prices[stk.top()]-prices[i];//:=
stk.pop();
}
stk.push(i);//small ,equal,and big
}
return ans;
}
};
给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。
这道题也一样,只不过是找右边第一个更大元素。
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
int n=temperatures.size();
stack<int>stk;
vector<int>ans(n,0);//巧妙的初始化方式
for(int i=0;i<n;i++){//扫描数组的元素
while(!stk.empty() && temperatures[i]>temperatures[stk.top()]){//遍历栈
ans[stk.top()]=i-stk.top();
stk.pop();
}
stk.push(i);
}
return ans;
}
};
初始化的方式可以根据具体的题目要求来看,做多了就熟练了。
至于栈定义好后要不要先加入第一个元素,完全根据个人喜好了,都可以。
刚开始写时最好将三种情况分开写,更容易理解。如:
for (int i = 1; i < T.size(); i++) {
if (T[i] < T[st.top()]) { // 情况一
st.push(i);
} else if (T[i] == T[st.top()]) { // 情况二
st.push(i);
} else {
while (!st.empty() && T[i] > T[st.top()]) { // 情况三
result[st.top()] = i - st.top();
st.pop();
}
st.push(i);
}
}
当然,类似的题目还有很多,比如接雨水问题等等,但基本的套路就是这样了,万变不离其宗,只要抓住本质,一切都可迎刃而解!