本文利用AI,对栈及其相关算法题进行系统学习。
栈(Stack)概述
栈(Stack) 是一种线性数据结构,遵循 先进后出(LIFO, Last In First Out) 的原则,即最后被插入的元素最先被移除。可以类比为一叠盘子,每次只能从最上面取盘子或放盘子。
栈通常有两种主要的操作:
- Push:将元素放入栈顶。
- Pop:将栈顶的元素移除。
除此之外,还有一些常见的辅助操作:
- Peek/Top:查看栈顶元素,但不移除它。
- IsEmpty:检查栈是否为空。
- Size:返回栈中元素的数量。
栈的复杂度分析
-
时间复杂度:
Push操作:O(1)(在栈顶插入元素,时间常数)Pop操作:O(1)(从栈顶删除元素,时间常数)Peek操作:O(1)(访问栈顶元素,时间常数)isEmpty操作:O(1)(判断栈是否为空,时间常数)
-
空间复杂度:
- 如果栈使用数组:
O(n)(n是栈的元素个数) - 如果栈使用链表:
O(n)(n是栈的元素个数)
- 如果栈使用数组:
栈的常见应用
- 括号匹配:在字符串中检查括号是否配对,常常使用栈来存储开放括号,遇到闭合括号时进行匹配。
- 深度优先搜索(DFS) :DFS 可以通过栈来实现,模拟递归的过程。
- 逆波兰表达式求值:通过栈来计算后缀表达式。
- 撤销操作(Undo) :栈可以用来存储操作历史,实现撤销操作。
- 算术表达式求值:处理中缀、后缀、前缀表达式时,可以利用栈简化计算过程。
AI刷题实践
简单的四则运算器
小F面临一个编程挑战:实现一个基本的计算器来计算简单的字符串表达式的值。该字符串表达式有效,并可能包含数字(0-9)、运算符+、-及括号()。注意,字符串中不包含空格。除法运算应只保留整数结果。请实现一个解析器计算这些表达式的值,且不使用任何内置的eval函数。
测试样例
样例1:
输入:
expression = "1+1"
输出:2
样例2:
输入:
expression = "3+4*5/(3+2)"
输出:7
样例3:
输入:
expression = "4+2*5-2/1"
输出:12
样例4:
输入:
expression = "(1+(4+5+2)-3)+(6+8)"
输出:23
样例5:
输入:
expression = "2*(5+5*2)/3+(6+8*3)"
输出:40
cpp解析及代码
// 写一个简单四则运算解析器,输入一个字符串表达式,输出计算结果。
#include <iostream>
#include <string>
#include <stack>
// 定义一个函数,用于计算两个操作数的结果
int applyOp(int a, int b, char op) {
switch (op) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/':
//除法要进行特殊处理:(1)除数不能为0 (2)向下取整
if (b == 0) {
throw "除数不能为0";
}
return a / b;
}
return 0;
}
// 定义一个函数,用于判断操作符的优先级
int precedence(char op) {
if (op == '+' || op == '-') {
return 1;
}
if (op == '*' || op == '/') {
return 2;
}
return 0;
}
int solution(std::string expression) {
// 定义一次操作数栈,用于存放操作数, 定义一个操作符栈,用于存放暂未确定是否可以计算的操作符
std::stack<int> values;
std::stack<char> ops;
//定义一个索引,用于遍历字符串
int i = 0;
while (i < expression.length()) {
// 如果是数字,直接入栈
if (expression[i] >= '0' && expression[i] <= '9') {
int val = 0;
while (i < expression.length() && expression[i] >= '0' && expression[i] <= '9') {
val = val * 10 + expression[i] - '0';
i++;
}
values.push(val);
i--; // 因为在循环结束后会执行i++,所以这里需要减1
}
// 如果是左括号,直接入栈
else if (expression[i] == '(') {
ops.push(expression[i]);
}
// 如果是右括号,弹出操作符栈中的操作符,直到遇到左括号
else if (expression[i] == ')') {
while (!ops.empty() && ops.top() != '(') {
int val2 = values.top();
values.pop();
int val1 = values.top();
values.pop();
char op = ops.top();
ops.pop();
values.push(applyOp(val1, val2, op));
}
ops.pop(); // 弹出左括号
}
// 如果是一般运算符,判断优先级,如果栈顶运算符的优先级不低于当前运算符,则弹出栈顶运算符进行计算,最后将当前运算符入栈
else if (expression[i] == '+' || expression[i] == '-' || expression[i] == '*' || expression[i] == '/') {
while (!ops.empty() && precedence(ops.top()) >= precedence(expression[i])) {
int val2 = values.top();
values.pop();
int val1 = values.top();
values.pop();
char op = ops.top();
ops.pop();
values.push(applyOp(val1, val2, op));
}
ops.push(expression[i]);
}
i++;
}
// 如果操作符栈不为空,继续计算
while (ops.size() >= 0) {
int val2 = values.top();
values.pop();
int val1 = values.top();
values.pop();
char op = ops.top();
ops.pop();
values.push(applyOp(val1, val2, op));
if (ops.empty()) {
break;
}
}
return values.top();
}
int main() {
// You can add more test cases here
std::cout << (solution("1+1") == 2) << std::endl;
std::cout << (solution("3+4*5/(3+2)") == 7) << std::endl;
std::cout << (solution("4+2*5-2/1") == 12) << std::endl;
std::cout << (solution("(1+(4+5+2)-3)+(6+8)") == 23) << std::endl;
return 0;
}
括号补全问题
小R有一个括号字符串 s,他想知道这个字符串是否是有效的。一个括号字符串如果满足以下条件之一,则是有效的:
- 它是一个空字符串;
- 它可以写成两个有效字符串的连接形式,即
AB; - 它可以写成
(A)的形式,其中A是有效字符串。
在每次操作中,小R可以在字符串的任意位置插入一个括号。你需要帮小R计算出,最少需要插入多少个括号才能使括号字符串 s 有效。
例如:当 s = "())" 时,小R需要插入一个左括号使字符串有效,结果为 1。
测试样例
样例1:
输入:
s = "())"
输出:1
样例2:
输入:
s = "((("
输出:3
样例3:
输入:
s = "()"
输出:0
样例4:
输入:
s = "()))(("
输出:4
cpp解析及
#include <iostream>
#include <string>
int solution(const std::string &s) {
// 记录未匹配的左括号数量
int left_unmatched = 0;
// 记录未匹配的右括号数量
int right_unmatched = 0;
for (char char : s) {
if (char == '(') {
// 遇到左括号,增加未匹配的左括号计数
left_unmatched += 1;
} else if (char == ')') {
if (left_unmatched > 0) {
// 如果有未匹配的左括号,则匹配掉一个
left_unmatched -= 1;
} else {
// 如果没有未匹配的左括号,说明需要一个额外的左括号
right_unmatched += 1;
}
}
}
// 总共需要插入的括号数是未匹配的左右括号数量之和
return left_unmatched + right_unmatched;
}
int main() {
std::cout << (solution("()") == 1) << std::endl;
std::cout << (solution("(((") == 3) << std::endl;
std::cout << (solution("()") == 0) << std::endl;
std::cout << (solution("()))((") == 4) << std::endl;
return 0;
}
总结
栈是一种重要的线性数据结构,主要用于存储和管理需要遵循“后进先出”顺序的数据。通过实现栈,我们可以轻松地解决许多实际问题,如递归、表达式求值、括号匹配等。栈的操作时间复杂度为常数级,因此在许多算法中都是高效且常用的数据结构。