682. 棒球比赛
682. 棒球比赛
题目链接(简单)
题目描述
你现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分。
比赛开始时,记录是空白的。你会得到一个记录操作的字符串列表 ops,其中 ops[i] 是你需要记录的第 i 项操作,ops 遵循下述规则:
- 整数
x:表示本回合新获得分数x; "+":表示本回合新获得的得分是前两次得分的总和。题目数据保证记录此操作时前面总是存在两个有效的分数;"D":表示本回合新获得的得分是前一次得分的两倍。题目数据保证记录此操作时前面总是存在一个有效的分数;"C":表示前一次得分无效,将其从记录中移除。题目数据保证记录此操作时前面总是存在一个有效的分数。
请你返回记录中所有得分的总和。
数据范围:
1 <= ops.length <= 1000ops[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":弹出栈顶。
最后把栈中所有元素求和即可。
方法:栈模拟
思路及解法
- 初始化一个空栈
stack; - 遍历每个操作
op:op == "+":stack.append(stack[-1] + stack[-2]);op == "D":stack.append(stack[-1] * 2);op == "C":stack.pop();- 否则:
stack.append(int(op));
- 返回
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. 简化路径(栈处理路径)