栈专题 | 豆包MarsCode AI 刷题

33 阅读5分钟

本文利用AI,对栈及其相关算法题进行系统学习。

栈(Stack)概述

栈(Stack) 是一种线性数据结构,遵循 先进后出(LIFO, Last In First Out) 的原则,即最后被插入的元素最先被移除。可以类比为一叠盘子,每次只能从最上面取盘子或放盘子。

栈通常有两种主要的操作:

  1. Push:将元素放入栈顶。
  2. Pop:将栈顶的元素移除。

除此之外,还有一些常见的辅助操作:

  • Peek/Top:查看栈顶元素,但不移除它。
  • IsEmpty:检查栈是否为空。
  • Size:返回栈中元素的数量。

栈的复杂度分析

  • 时间复杂度

    • Push 操作:O(1)(在栈顶插入元素,时间常数)
    • Pop 操作:O(1)(从栈顶删除元素,时间常数)
    • Peek 操作:O(1)(访问栈顶元素,时间常数)
    • isEmpty 操作:O(1)(判断栈是否为空,时间常数)
  • 空间复杂度

    • 如果栈使用数组:O(n)(n是栈的元素个数)
    • 如果栈使用链表:O(n)(n是栈的元素个数)

栈的常见应用

  1. 括号匹配:在字符串中检查括号是否配对,常常使用栈来存储开放括号,遇到闭合括号时进行匹配。
  2. 深度优先搜索(DFS) :DFS 可以通过栈来实现,模拟递归的过程。
  3. 逆波兰表达式求值:通过栈来计算后缀表达式。
  4. 撤销操作(Undo) :栈可以用来存储操作历史,实现撤销操作。
  5. 算术表达式求值:处理中缀、后缀、前缀表达式时,可以利用栈简化计算过程。

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,他想知道这个字符串是否是有效的。一个括号字符串如果满足以下条件之一,则是有效的:

  1. 它是一个空字符串;
  2. 它可以写成两个有效字符串的连接形式,即 AB
  3. 它可以写成 (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;
}

总结

栈是一种重要的线性数据结构,主要用于存储和管理需要遵循“后进先出”顺序的数据。通过实现栈,我们可以轻松地解决许多实际问题,如递归、表达式求值、括号匹配等。栈的操作时间复杂度为常数级,因此在许多算法中都是高效且常用的数据结构。