questions B (backspace - burst-balloons)
This commit is contained in:
199
backend/data/questions/baseball-game.yaml
Normal file
199
backend/data/questions/baseball-game.yaml
Normal file
@@ -0,0 +1,199 @@
|
||||
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
|
||||
|
||||
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.
|
||||
Reference in New Issue
Block a user