682. 棒球比赛

682. 棒球比赛

题目链接(简单)

题目描述

你现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分。

比赛开始时,记录是空白的。你会得到一个记录操作的字符串列表 ops,其中 ops[i] 是你需要记录的第 i 项操作,ops 遵循下述规则:

  • 整数 x:表示本回合新获得分数 x;
  • "+":表示本回合新获得的得分是前两次得分的总和。题目数据保证记录此操作时前面总是存在两个有效的分数;
  • "D":表示本回合新获得的得分是前一次得分的两倍。题目数据保证记录此操作时前面总是存在一个有效的分数;
  • "C":表示前一次得分无效,将其从记录中移除。题目数据保证记录此操作时前面总是存在一个有效的分数。

请你返回记录中所有得分的总和。

数据范围:

  • 1 <= ops.length <= 1000
  • ops[i] 为 "C"、"D"、"+",或者一个表示整数的字符串
  • 整数范围是 [-3 * 10^4, 3 * 10^4]
  • 对于 "+" 操作,题目保证前面总是存在两个有效分数
  • 对于 "C" 和 "D" 操作,题目保证前面总是存在一个有效分数

示例

示例 1:

输入: ops = ["5","2","C","D","+"]
输出: 30
解释:

"5" - 记录加 5,记录现在是 [5]
"2" - 记录加 2,记录现在是 [5, 2]
"C" - 使前一次得分的记录无效并将其移除,记录现在是 [5]
"D" - 记录加 2 * 5 = 10,记录现在是 [5, 10]
"+" - 记录加 5 + 10 = 15,记录现在是 [5, 10, 15]
所有得分的总和 5 + 10 + 15 = 30

示例 2:

输入: ops = ["5","-2","4","C","D","9","+","+"]
输出: 27
解释:

"5"  - 记录加 5,记录现在是 [5]
"-2" - 记录加 -2,记录现在是 [5, -2]
"4"  - 记录加 4,记录现在是 [5, -2, 4]
"C"  - 使前一次得分的记录无效并将其移除,记录现在是 [5, -2]
"D"  - 记录加 2 * -2 = -4,记录现在是 [5, -2, -4]
"9"  - 记录加 9,记录现在是 [5, -2, -4, 9]
"+"  - 记录加 -4 + 9 = 5,记录现在是 [5, -2, -4, 9, 5]
"+"  - 记录加 9 + 5 = 14,记录现在是 [5, -2, -4, 9, 5, 14]
所有得分的总和 5 + -2 + -4 + 9 + 5 + 14 = 27

示例 3:

输入: ops = ["1"]
输出: 1

核心思路

所有操作都只影响记录列表的末尾:

  • 添加新分数 → 在末尾追加;
  • "C" 撤销 → 弹出末尾;
  • "D" 和 "+" 都依赖末尾的元素。

这正是栈的典型场景:后进先出,只在顶部操作。

遍历 ops,对每个操作分情况处理:

  • 若是整数:转为 int 后压栈;
  • 若是 "+":取栈顶两个元素之和,压栈;
  • 若是 "D":取栈顶元素 × 2,压栈;
  • 若是 "C":弹出栈顶。

最后把栈中所有元素求和即可。


方法:栈模拟

思路及解法

  1. 初始化一个空栈 stack;
  2. 遍历每个操作 op:
    • op == "+":stack.append(stack[-1] + stack[-2]);
    • op == "D":stack.append(stack[-1] * 2);
    • op == "C":stack.pop();
    • 否则:stack.append(int(op));
  3. 返回 sum(stack)。

为什么用栈:

  • 所有操作都只依赖最近的 1~2 个元素;
  • 栈顶就是「前一次得分」,栈顶下面就是「前两次得分」;
  • 撤销 "C" 就是弹出栈顶。

代码

class Solution:
    def calPoints(self, ops: list[str]) -> int:
        stack = []

        for op in ops:
            if op == "+":
                stack.append(stack[-1] + stack[-2])
            elif op == "D":
                stack.append(stack[-1] * 2)
            elif op == "C":
                stack.pop()
            else:
                stack.append(int(op))

        return sum(stack)

复杂度分析

  • 时间复杂度:$O(n)$,遍历 ops 一次,每个操作 $O(1)$。
  • 空间复杂度:$O(n)$,栈最多存储 n 个分数。

关键细节

1. 判断顺序很重要

必须先判断 "+"、"D"、"C" 这些字符串操作,最后才处理数字字符串。

if op == "+":        # 先判断操作符
    ...
elif op == "D":
    ...
elif op == "C":
    ...
else:                # 剩下的才是数字
    stack.append(int(op))

如果先尝试 int(op),遇到 "+" 会抛 ValueError。

2. "C" 只是弹出,不影响已累加的和

一种常见错误是:维护一个「当前总和」变量,"C" 时减去被删除的分数。这样也可以,但需要额外记录被删除的值,容易出错。

直接用栈 + 最后 sum 更简洁。

3. "+" 和 "D" 的依赖关系

  • "+" 依赖栈顶两个元素 stack[-1] 和 stack[-2];
  • "D" 依赖栈顶一个元素 stack[-1]。

题目保证操作合法(前面有足够的有效分数),所以不需要额外检查 len(stack)。

4. 负数处理

操作里可能出现负数(如示例 2 的 "-2"),int("-2") 正确解析为 -2,栈内可以直接存负数,sum 也没问题。

5. 与 LC 150(逆波兰表达式)的区别

LC 150 逆波兰表达式 LC 682 棒球比赛
运算符 +、-、*、/ +、D、C
+ 的含义 两个操作数相加 前两次得分相加
D 的含义 无 前一次得分 × 2
C 的含义 无 撤销前一次

两者都用栈模拟,但 + 的语义不同。LC 150 是标准后缀表达式,LC 682 是自定义规则。

6. 一种「不存栈、只维护总和」的写法

class Solution:
    def calPoints(self, ops: list[str]) -> int:
        stack = []
        total = 0

        for op in ops:
            if op == "+":
                val = stack[-1] + stack[-2]
                stack.append(val)
                total += val
            elif op == "D":
                val = stack[-1] * 2
                stack.append(val)
                total += val
            elif op == "C":
                total -= stack.pop()
            else:
                val = int(op)
                stack.append(val)
                total += val

        return total

优点:省去最后 sum 的一次遍历。
缺点:"C" 时要用 total -= stack.pop(),需要小心顺序(先 pop 拿到值,再减)。

两种写法都正确,直接用 sum(stack) 更简洁、更不容易写错。


总结

  • 核心思路:用栈模拟记录,所有操作都只在栈顶进行;
  • 四种操作:
    • 整数:压栈;
    • "+":stack[-1] + stack[-2] 压栈;
    • "D":stack[-1] * 2 压栈;
    • "C":弹出栈顶;
  • 答案:sum(stack);
  • 时间复杂度 $O(n)$,空间复杂度 $O(n)$;
  • 通用套路:「只操作末尾 + 撤销」→ 栈。

相关题目

  • LC 150. 逆波兰表达式求值(栈模拟后缀表达式)
  • LC 20. 有效的括号(栈匹配)
  • LC 1047. 删除字符串中的所有相邻重复项(栈消除)
  • LC 71. 简化路径(栈处理路径)

682. 棒球比赛
https://mingsm17518.github.io/2026/10/22/刷题笔记/Leetcode/682. 棒球比赛/
作者
Ming
发布于
2026年10月22日
更新于
2026年10月22日
许可协议