222 lines
9.4 KiB
YAML
222 lines
9.4 KiB
YAML
title: Baseball Game
|
|
slug: baseball-game
|
|
difficulty: easy
|
|
leetcode_id: 682
|
|
leetcode_url: https://leetcode.com/problems/baseball-game/
|
|
categories:
|
|
- arrays
|
|
- stack
|
|
patterns:
|
|
- monotonic-stack
|
|
|
|
function_signature: "def cal_points(operations: list[str]) -> int:"
|
|
|
|
test_cases:
|
|
visible:
|
|
- input: { operations: ["5", "2", "C", "D", "+"] }
|
|
expected: 30
|
|
- input: { operations: ["5", "-2", "4", "C", "D", "9", "+", "+"] }
|
|
expected: 27
|
|
- input: { operations: ["1", "C"] }
|
|
expected: 0
|
|
hidden:
|
|
- input: { operations: ["1"] }
|
|
expected: 1
|
|
- input: { operations: ["5", "10", "+"] }
|
|
expected: 30
|
|
- input: { operations: ["3", "D", "D", "D"] }
|
|
expected: 45
|
|
- input: { operations: ["-5", "10", "+", "D"] }
|
|
expected: 20
|
|
- input: { operations: ["1", "2", "3", "4", "5"] }
|
|
expected: 15
|
|
|
|
description: |
|
|
You are keeping the scores for a baseball game with strange rules. At the beginning of the game, you start with an empty record.
|
|
|
|
You are given a list of strings `operations`, where `operations[i]` is the i<sup>th</sup> operation you must apply to the record and is one of the following:
|
|
|
|
- An integer `x` — Record a new score of `x`.
|
|
- `'+'` — Record a new score that is the sum of the previous two scores.
|
|
- `'D'` — Record a new score that is the double of the previous score.
|
|
- `'C'` — Invalidate the previous score, removing it from the record.
|
|
|
|
Return *the sum of all the scores on the record after applying all the operations*.
|
|
|
|
The test cases are generated such that the answer and all intermediate calculations fit in a **32-bit** integer and that all operations are valid.
|
|
|
|
constraints: |
|
|
- `1 <= operations.length <= 1000`
|
|
- `operations[i]` is `"C"`, `"D"`, `"+"`, or a string representing an integer in the range `[-3 * 10^4, 3 * 10^4]`
|
|
- For operation `"+"`, there will always be at least two previous scores on the record
|
|
- For operations `"C"` and `"D"`, there will always be at least one previous score on the record
|
|
|
|
examples:
|
|
- input: 'ops = ["5","2","C","D","+"]'
|
|
output: "30"
|
|
explanation: |
|
|
"5" - Add 5 to the record, record is now [5].
|
|
"2" - Add 2 to the record, record is now [5, 2].
|
|
"C" - Invalidate and remove the previous score, record is now [5].
|
|
"D" - Add 2 * 5 = 10 to the record, record is now [5, 10].
|
|
"+" - Add 5 + 10 = 15 to the record, record is now [5, 10, 15].
|
|
The total sum is 5 + 10 + 15 = 30.
|
|
- input: 'ops = ["5","-2","4","C","D","9","+","+"]'
|
|
output: "27"
|
|
explanation: |
|
|
"5" - Add 5 to the record, record is now [5].
|
|
"-2" - Add -2 to the record, record is now [5, -2].
|
|
"4" - Add 4 to the record, record is now [5, -2, 4].
|
|
"C" - Invalidate and remove the previous score, record is now [5, -2].
|
|
"D" - Add 2 * -2 = -4 to the record, record is now [5, -2, -4].
|
|
"9" - Add 9 to the record, record is now [5, -2, -4, 9].
|
|
"+" - Add -4 + 9 = 5 to the record, record is now [5, -2, -4, 9, 5].
|
|
"+" - Add 9 + 5 = 14 to the record, record is now [5, -2, -4, 9, 5, 14].
|
|
The total sum is 5 + -2 + -4 + 9 + 5 + 14 = 27.
|
|
- input: 'ops = ["1","C"]'
|
|
output: "0"
|
|
explanation: |
|
|
"1" - Add 1 to the record, record is now [1].
|
|
"C" - Invalidate and remove the previous score, record is now [].
|
|
Since the record is empty, the total sum is 0.
|
|
|
|
explanation:
|
|
intuition: |
|
|
Imagine you're keeping score on a notepad where you can only add entries at the bottom and erase the most recent entry. This is exactly how a **stack** works — Last In, First Out (LIFO).
|
|
|
|
The key insight is that every operation either:
|
|
- **Adds** a new score to the end of the record (integer, `+`, `D`)
|
|
- **Removes** the most recent score (`C`)
|
|
|
|
This matches perfectly with stack operations: `push` to add and `pop` to remove. The stack always maintains the current valid scores in order, so we can easily access the last one or two elements when needed.
|
|
|
|
Think of it like this: the record is a stack of paper slips. When you get a new score, you place a slip on top. When you need to double (`D`) or sum (`+`), you peek at the top slip(s), calculate, and add a new slip. When you invalidate (`C`), you simply remove the top slip.
|
|
|
|
approach: |
|
|
We solve this using a **Stack Simulation** approach:
|
|
|
|
**Step 1: Initialise an empty stack**
|
|
|
|
- `record`: An empty list to act as our stack, storing valid scores
|
|
|
|
|
|
|
|
**Step 2: Process each operation**
|
|
|
|
- For each operation in the input:
|
|
- If it's an **integer**: Convert to int and push onto the stack
|
|
- If it's `"+"`: Add the sum of the last two scores (`record[-1] + record[-2]`) to the stack
|
|
- If it's `"D"`: Add double the last score (`2 * record[-1]`) to the stack
|
|
- If it's `"C"`: Pop the last score from the stack
|
|
|
|
|
|
|
|
**Step 3: Return the sum**
|
|
|
|
- Return `sum(record)` — the sum of all remaining valid scores
|
|
|
|
|
|
|
|
The stack naturally maintains only the valid scores at any point. Since the problem guarantees all operations are valid (e.g., `+` always has at least two previous scores), we don't need additional boundary checks.
|
|
|
|
common_pitfalls:
|
|
- title: Not Recognising the Stack Pattern
|
|
description: |
|
|
Some developers try to use complex conditionals or maintain separate variables instead of recognising this as a classic stack problem.
|
|
|
|
The operations map directly to stack operations:
|
|
- Integer → `push(value)`
|
|
- `"+"` → `push(peek(-1) + peek(-2))`
|
|
- `"D"` → `push(2 * peek(-1))`
|
|
- `"C"` → `pop()`
|
|
|
|
Once you see this mapping, the solution becomes straightforward.
|
|
wrong_approach: "Complex state tracking with multiple variables"
|
|
correct_approach: "Use a stack to track the record"
|
|
|
|
- title: Integer Parsing Edge Cases
|
|
description: |
|
|
Remember that scores can be **negative** (e.g., `"-2"`). When checking if an operation is an integer, don't just check if the first character is a digit — it could be a minus sign.
|
|
|
|
In Python, using `try/except` with `int()` or checking with `lstrip('-').isdigit()` handles this correctly. Alternatively, check if the operation is not one of the three special characters.
|
|
wrong_approach: "Checking only if first char is digit"
|
|
correct_approach: "Check if operation is not in {'C', 'D', '+'}"
|
|
|
|
- title: Off-by-One in Index Access
|
|
description: |
|
|
When accessing the last two elements for the `"+"` operation, remember:
|
|
- `record[-1]` is the most recent (last) score
|
|
- `record[-2]` is the second-to-last score
|
|
|
|
The problem guarantees at least two scores exist for `"+"`, so this access is always safe.
|
|
|
|
key_takeaways:
|
|
- "**Stack pattern recognition**: When operations involve 'most recent' or 'undo', think stack"
|
|
- "**Simulation problems**: Walk through the operations step-by-step, maintaining state as you go"
|
|
- "**Python negative indexing**: `list[-1]` and `list[-2]` provide clean access to recent elements"
|
|
- "**Trust the constraints**: When the problem guarantees valid operations, you can skip defensive checks"
|
|
|
|
time_complexity: "O(n). We process each operation exactly once, where `n` is the length of `operations`."
|
|
space_complexity: "O(n). In the worst case, all operations are integers, so the stack stores `n` elements."
|
|
|
|
solutions:
|
|
- approach_name: Stack Simulation
|
|
is_optimal: true
|
|
code: |
|
|
def cal_points(operations: list[str]) -> int:
|
|
# Stack to track valid scores
|
|
record = []
|
|
|
|
for op in operations:
|
|
if op == '+':
|
|
# Sum of the last two scores
|
|
record.append(record[-1] + record[-2])
|
|
elif op == 'D':
|
|
# Double the last score
|
|
record.append(2 * record[-1])
|
|
elif op == 'C':
|
|
# Remove the last score
|
|
record.pop()
|
|
else:
|
|
# It's an integer score
|
|
record.append(int(op))
|
|
|
|
# Return sum of all valid scores
|
|
return sum(record)
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Single pass through operations, each operation is O(1).
|
|
|
|
**Space Complexity:** O(n) — Stack can grow to size n in the worst case.
|
|
|
|
We simulate the game by processing each operation and maintaining a stack of valid scores. The stack's LIFO property perfectly matches the problem's requirement to access and modify the most recent scores.
|
|
|
|
- approach_name: Stack with Running Sum
|
|
is_optimal: false
|
|
code: |
|
|
def cal_points(operations: list[str]) -> int:
|
|
record = []
|
|
total = 0
|
|
|
|
for op in operations:
|
|
if op == '+':
|
|
score = record[-1] + record[-2]
|
|
elif op == 'D':
|
|
score = 2 * record[-1]
|
|
elif op == 'C':
|
|
# Subtract the removed score from total
|
|
total -= record.pop()
|
|
continue
|
|
else:
|
|
score = int(op)
|
|
|
|
record.append(score)
|
|
total += score
|
|
|
|
return total
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Single pass through operations.
|
|
|
|
**Space Complexity:** O(n) — Same stack storage.
|
|
|
|
This variation maintains a running total, adding scores as they're pushed and subtracting when popped. While it avoids calling `sum()` at the end, the overall complexity is the same. The first approach is cleaner and preferred.
|