一、经验总结
何时使用栈结构解题?
- 做过相似的使用栈结构解得的题目
- 嵌套处理:在从前向后处理的过程中,由于之后内容的不确定性而导致当前操作不能贸然进行,需要先进行保存,直到遇到区间结束标志(如’)')此时才能处理最近区间的内容。从前往后存,从后往前取,正好是栈后进先出的特点。
二、相关编程题
2.1 删除字符串中的所有相邻重复项
题目链接
1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode)
题目描述
算法原理
编写代码
class Solution {
public:
string removeDuplicates(string s) {
string ret; //用数组模拟栈结构
for(auto ch : s)
{
if(!ret.empty() && ch == ret.back())
ret.pop_back();
else
ret.push_back(ch);
}
return ret;
}
};
2.2 比较含退格的字符串
题目链接
844. 比较含退格的字符串 - 力扣(LeetCode)
题目描述
算法原理
同上,略
编写代码
class Solution {
public:
bool backspaceCompare(string s, string t) {
return ChangeStr(s) == ChangeStr(t);
}
string ChangeStr(const string& str)
{
string ret;
for(auto ch : str)
{
if(ch == '#')
{
if(!ret.empty()) ret.pop_back();
}
else
ret+=ch;
}
return ret;
}
};
2.3 基本计算器 II(无括号)
题目链接
227. 基本计算器 II - 力扣(LeetCode)
题目描述
算法原理
编写代码
class Solution {
public:
int calculate(string s) {
stack<int> st;
char op = '+';
int i = 0;
while(i < s.size())
{
if (s[i] == ' ') ++i;
else if (s[i] >= '0' && s[i] <= '9')
{
int num = 0;
while (i < s.size() && s[i] >= '0' && s[i] <= '9')
{
num = num * 10 + (s[i]-'0');
++i;
}
switch (op)
{
case '+':
st.push(num);
break;
case '-':
st.push(-num);
break;
case '*':
st.top()*=num;
break;
case '/':
st.top()/=num;
break;
}
}
else op = s[i++];
}
int ret = 0;
while(!st.empty())
{
ret += st.top();
st.pop();
}
return ret;
}
};
2.4 逆波兰表达式求值
题目链接
150. 逆波兰表达式求值 - 力扣(LeetCode)
题目描述
算法原理
后缀表达式求值
后缀表达式,也称为逆波兰表达式,是一种没有操作符优先级的表达式(也没有左右括号),因此求值过程相对直接。求值过程通常涉及使用一个栈来辅助存储操作数,并按照以下步骤进行:
- 遍历后缀表达式中的每个元素。
- 如果遇到操作数(通常是数字),则将其压入栈中。
- 如果遇到操作符,则从栈中弹出两个操作数进行计算,并将结果压回栈中。
- 这个过程持续直到后缀表达式中的所有元素都被处理,最终栈中剩下的就是表达式的计算结果。
逆波兰 - 下(后缀表达式计算结果)_哔哩哔哩_bilibili
编写代码
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> nums; //使用一个栈来辅助存储操作数
for(auto &str : tokens)
{
if(IsNum(str))
{
nums.push(stoi(str));
}
else
{
int right = nums.top(); //后进先出,右操作数先出栈
nums.pop();
int left = nums.top();
nums.pop();
switch(str[0])
{
case '+':
nums.push(left+right);
break;
case '-':
nums.push(left-right);
break;
case '*':
nums.push(left*right);
break;
case '/':
nums.push(left/right);
break;
}
}
}
return nums.top();
}
bool IsNum(const string &str)
{
if(str[0] == '-')
{
if(str.size() > 1) return true;
}
else if(str[0] >= '0' && str[0] <= '9')
{
return true;
}
return false;
}
};
2.5 中缀表达式求值(有括号)
题目链接
表达式求值_牛客题霸_牛客网 (nowcoder.com)
题目描述
算法原理
解法一:中缀表达式转后缀表达式,再运算
在中缀变后缀时,操作数的顺序不会发生变化,只有运算符的顺序可能发生变化。同时又没有括号。所以在转换的过程中,只要碰到操作数,可以直接输出,而遇到运算符和括号进行相应的处理即可。
转换原则如下:
1.从左到右读取一个中序表达式。
2.若读取的是操作数,则直接输出。
3.若读取的是运算符,分三种情况。
-
该运算符为左括号( ,则直接存入堆栈。 (等待右括号出现;提高优先级:隔断左括号之前的运算符优先级比较)
-
该运算符为右括号),则输出堆栈中的运算符,直到取出左括号为止。 (提高优先级:优先处理括号内的运算)
-
该运算符为非括号运算符,则与堆栈顶端的运算符做优先权比较:
-
- 如果栈为空或当前运算符优先级高于栈顶运算符,则直接入栈。(不能确定后面的运算符优先级是否更高,入栈等待)
- 如果当前运算符优先级低于等于栈顶运算符,则弹出栈顶运算符并输出,直到当前运算符优先级大于栈顶运算符或者栈空为止,然后将当前运算符入栈。(能够确定栈顶运算符的优先级比后面的运算符(当前)优先级更高,出栈运算)
4.当表达式已经读取完成,而堆栈中尚有运算符时,则依次序取出运算符,直到堆栈为空,由此得到的结果就是中缀表达式转换成的后缀表达式。
5.后缀表达式求值参考上一题。
逆波兰 - 上(中缀表达式 转 后缀表达式)_哔哩哔哩_bilibili
解法二:数字栈和算符栈
中缀表达式求值通常涉及使用栈来处理运算符和操作数。以下是中缀表达式求值的基本步骤:
-
定义两个栈,一个用于存储操作数,另一个用于存储运算符。
-
遍历中缀表达式的每个字符,如果遇到操作数(数字),则将其压入操作数栈。
-
如果遇到运算符,则将其与运算符栈的栈顶元素比较优先级:
-
- 如果运算符的优先级高于栈顶元素的优先级或运算符栈为空,将其压入运算符栈。(不能确定后面的运算符优先级是否更高,入栈等待)
- 如果运算符的优先级低于或者等于栈顶元素的优先级,则从操作数栈弹出两个操作数进行计算,并将结果压入操作数栈,同时将运算符栈中优先级低于或等于当前运算符的元素依次弹出并处理,直到遇到一个优先级高于当前运算符的元素或栈为空,再将运算符压入运算符栈。(能够确定栈顶运算符的优先级更高,出栈运算)
-
如果遇到左括号“(”,则直接将其压入运算符栈。(提高优先级:隔断左括号之前的运算符优先级比较;等待右括号出现)
-
如果遇到右括号“)”,则从运算符栈弹出并处理运算符,直到遇到左括号“(”为止。(提高优先级:优先处理括号内的运算)
-
遍历完成后,如果运算符栈中还有元素,则从操作数栈弹出两个操作数进行计算,并将结果压回操作数栈。
-
最后,操作数栈中剩下的元素(如果有)就是表达式的求值结果。
这种方法的关键在于正确处理运算符的优先级和括号,其实底层原理与解法一相同。
中缀表达式的计算_哔哩哔哩_bilibili
编写代码
//解法一:中缀表达式转后缀表达式,再运算
#include <cmath>
class Solution {
public:
int solve(string s) {
stack<char> ops;
unordered_map<char, int> op_level = {{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}}; //运算符的优先级
vector<string> tokens; //存储后缀表达式
//中缀转后缀
int i = 0;
while(i < s.size())
{
if(IsNum(s[i]))
{
string num;
while(i < s.size() && IsNum(s[i]))
{
num += s[i++];
}
tokens.push_back(num);
}
else if (s[i] == '(')
{
ops.push(s[i++]);
}
else if (s[i] == ')')
{
while(ops.top() != '(')
{
tokens.push_back(string(1, ops.top()));
ops.pop();
}
ops.pop();
++i;
}
else
{
while (!ops.empty() && ops.top() != '(' && op_level[s[i]] <= op_level[ops.top()])
{
tokens.push_back(string(1, ops.top()));
ops.pop();
}
ops.push(s[i++]);
}
}
while(!ops.empty())
{
tokens.push_back(string(1, ops.top()));
ops.pop();
}
//后缀表达式求值
return evalRPN(tokens); //这里复用上一题的代码,直接复制过来使用就行
}
bool IsNum(char ch)
{
return ch >= '0' && ch <= '9';
}
};
//解法二:数字栈和算符栈
class Solution {
stack<char> op_st; //算符栈
stack<int> num_st; //数字栈
unordered_map<char, int> op_level = {{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}}; //运算符的优先级
public:
int solve(string s) {
int i = 0;
while (i < s.size()) {
if (IsNum(s[i]))
{
int num = 0;
while (i < s.size() && IsNum(s[i]))
num = num * 10 + (s[i++] - '0');
num_st.push(num);
}
else if (s[i] == '(')
{
op_st.push(s[i++]);
}
else if (s[i] == ')')
{
while (op_st.top() != '(') calc();
op_st.pop();
++i;
}
else
{
while (!op_st.empty() && op_st.top() != '(' && op_level[s[i]] <= op_level[op_st.top()]) calc();
op_st.push(s[i++]);
}
}
while(!op_st.empty()) calc();
return num_st.top();
}
bool IsNum(char ch) {
return ch >= '0' && ch <= '9';
}
//calc的工作是从栈中取出2个操作数和1个操作符进行运算,并将结果压入数字栈
void calc() {
char op = op_st.top();
op_st.pop();
int right = num_st.top();
num_st.pop();
int left = num_st.top();
num_st.pop();
int ret = 0;
switch (op) {
case '+':
ret = left + right;
break;
case '-':
ret = left - right;
break;
case '*':
ret = left * right;
break;
case '/':
ret = left / right;
break;
}
num_st.push(ret);
}
};
2.6 字符串解码
题目链接
394. 字符串解码 - 力扣(LeetCode)
题目描述
算法原理
编写代码
class Solution {
public:
string decodeString(string s) {
stack<string> str_st;
stack<int> num_st;
str_st.push("");
int i = 0;
while(i < s.size())
{
if(IsNum(s[i]))
{
int num = 0;
while(i < s.size() && IsNum(s[i]))
{
num = num*10+(s[i]-'0');
++i;
}
num_st.push(num);
}
if(s[i] == '[')
{
++i;
string tmp;
while(i < s.size() && IsLetter(s[i]))
{
tmp += s[i];
++i;
}
str_st.push(tmp);
}
if(s[i] == ']')
{
string top = str_st.top();
str_st.pop();
int k = num_st.top();
num_st.pop();
while(k--) str_st.top()+=top;
++i;
}
if(IsLetter(s[i]))
{
str_st.top()+=s[i];
++i;
}
}
return str_st.top();
}
bool IsNum(char ch)
{
return ch >= '0' && ch <='9';
}
bool IsLetter(char ch)
{
return ch >= 'a' && ch <= 'z';
}
};
2.7 验证栈序列
题目链接
946. 验证栈序列 - 力扣(LeetCode)
题目描述
算法原理
编写代码
class Solution {
public:
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
stack<int> st;
int j = 0;
for(int i = 0; i < pushed.size(); ++i)
{
st.push(pushed[i]);
while(!st.empty() && st.top() == popped[j])
{
st.pop();
++j;
}
}
return j == popped.size();
}
};