基本概念
定义:栈是一种抽象的数据类型,它遵循先进后出的原则。这意味着最后被添加到栈中的元素将首先被移除。
栈可以类比为一叠盘子,你只能从最上面添加或移除盘子。
栈的基本操作
栈支持以下基本操作:
- Push(入栈) :向栈中添加一个新元素。该操作将元素添加到栈顶。
- Pop(出栈) :从栈中移除栈顶的元素,并返回该元素。如果栈为空,则无法执行此操作。
所以每次需要pop的时候,要检查一下是否为空栈。
- Peek(查看栈顶元素) :查看栈顶元素但不移除它。如果栈为空,则没有元素可查看。
- Empty(检查是否为空) :判断栈是否为空。当栈中没有任何元素时返回真(true),否则返回假(false)。
- Size(获取栈大小) :返回栈中元素的数量。
栈在实际问题中的应用
- 括号匹配:用于验证代码或数学公式中的括号是否正确配对。
- 单调栈:用于寻找数组中每个元素右边或左边第一个比它大或小的元素。
- 最小栈:设计支持常数时间内获取最小值的栈。
- 回溯算法:解决迷宫问题、八皇后问题等,利用栈保存路径信息以便回溯。
- 函数调用堆栈:编程语言使用栈来管理函数调用,存储返回地址、局部变量等信息。
- 表达式求值与转换:如将中缀表达式转化为后缀表达式(逆波兰表示法),或者直接计算后缀表达式的值。
1. 括号匹配
题目:20.有效的括号
理解题目,左括号必须用相同类型的右括号闭合,则说明字符串的长度是偶数,首先排除奇数的可能。
左括号必须以正确的顺序闭合,并且每个右括号都有一个对应的相同类型的左括号。 我们可以利用栈“先进后出”的特点来解决。
解决方案
具体步骤如下:
-
初始化一个空栈。
-
遍历字符串中的每一个字符:
- 如果是左括号,则将其压入栈中。
- 如果是右括号,检查栈顶元素是否为对应的左括号。如果是,则弹出栈顶元素;如果不是,则说明括号不匹配,返回
false。
-
最终如果栈为空,说明所有括号都匹配;否则,返回
false。
class Solution {
public:
bool isValid(string s) {
if(s.length() % 2 != 0) return false;
stack<char> myStack;
for(char ch : s){
switch(ch){
case '(':
myStack.push(')');
break;
case '[':
myStack.push(']');
break;
case '{':
myStack.push('}');
break;
case ')':
if(!myStack.empty()){
if(')' != myStack.top()) return false;
myStack.pop();
break;
}else{
return false;
}
case ']':
if(!myStack.empty()){
if(']' != myStack.top()) return false;
myStack.pop();
break;
}else{
return false;
}
case '}':
if(!myStack.empty()){
if('}' != myStack.top()) return false;
myStack.pop();
break;
}else{
return false;
}
default: break;
}
}
if(!myStack.empty()) return false; // 排除只有左边的括号情况
return true;
}
};
2. 单调栈
什么是单调栈?
单调栈是指栈内元素保持某种顺序(递增或递减)的栈。通过维护这种单调性,可以高效地解决问题,比如查找数组中每个元素右侧或左侧第一个更大或更小的元素。
应用案例
- 下一个更大元素 I 和 II:通过单调递减栈找到每个元素右侧的第一个更大元素。
下一个更大元素I: leetcode.cn/submissions…
下一个更大元素II: leetcode.cn/submissions…
- 每日温度:对于给定的每天温度,找出至少需要等待多少天才能遇到更高温度。
- 商品折扣后的最终价格:基于相邻更低的价格为当前商品提供折扣。
我们来讲解每日温度这题:
给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。
示例 1:
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
示例 2:
输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]
示例 3:
输入: temperatures = [30,60,90]
输出: [1,1,0]
为什么使用单调栈?
解决这个问题时,我们可以利用单调递减栈(从栈底到栈顶元素逐渐减少)来高效地找到每个温度对应的“下一个更高温度”的天数。通过这种方法,我们可以避免对每个元素进行两两比较,从而将时间复杂度优化至O(n),其中n是输入数组的长度。
单调栈的工作原理
- 遍历温度数组:从左向右遍历数组中的每一个温度。
- 维护单调性:使用栈来存储尚未找到下一个更高温度的那些天的索引,并保持栈内温度的单调递减特性。
- 更新结果:当遇到一个新温度时,如果它比栈顶元素(即之前某一天的温度)高,则说明找到了栈顶元素对应那天的“下一个更高温度”,计算等待天数并更新结果数组;同时,弹出栈顶元素,继续检查新的栈顶直到栈为空或当前温度不大于栈顶元素为止。
- 入栈操作:无论是否进行了弹出操作,最后都要将当前天的索引压入栈中,以维持栈的单调性。
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
// 单调栈算法
int n = temperatures.size();
stack<int> indexStack; //存储索引
vector<int> days(n,0); //初始化长度为n, 值都为0
for(int i = 0; i < n; i++){
while(!index.empty() && temperatures[i] > temperatures[indexStack.top()]){
int preIndex = indexStack.top();
indexStack.pop();
days[preIndex] = i - preIndex;
}
index.push(i);
}
return days;
}
};
代码解析
-
初始化:首先初始化一个与
temperatures等长的结果数组days,默认值设为0,因为如果没有找到比当天更高的温度,默认答案就是0。然后创建一个空栈indexStack用于存放尚未找到答案的那些天的索引。 -
遍历与更新:遍历
temperatures数组,对于每一天的温度:- 如果栈非空并且当前温度大于栈顶索引对应的温度,那么就找到了栈顶元素对应的那一天的答案,即
i - prevIndex天后出现了更高的温度。 - 弹出栈顶索引,继续检查栈顶直至条件不再满足。
- 最后,无论是否有更新操作,都将当前索引压入栈中,以保持栈的单调性。
- 如果栈非空并且当前温度大于栈顶索引对应的温度,那么就找到了栈顶元素对应的那一天的答案,即
这种方法保证了每个元素最多只会被压入和弹出栈一次,因此整体的时间复杂度为O(n),非常适合处理大规模数据。