Simulate a baseball scoring system using a stack to process operations and compute the final score.
Problem Restatement
We are given a list of operations called ops.
These operations describe scores in a baseball game.
We process the operations from left to right and maintain a record of valid scores.
The operations are:
| Operation | Meaning |
|---|---|
Integer x | Record a new score x |
"+" | Record a score equal to the sum of the previous two valid scores |
"D" | Record a score equal to double the previous valid score |
"C" | Remove the previous valid score |
After processing all operations, return the sum of all valid scores.
The official examples include ["5","2","C","D","+"] -> 30 and ["5","-2","4","C","D","9","+","+"] -> 27. (leetcode.com)
Input and Output
| Item | Meaning |
|---|---|
| Input | A list of strings ops |
| Output | Integer total score |
| Valid operations | Integer, "+", "D", "C" |
| Guarantee | Operations are always valid |
Example function shape:
def calPoints(ops: list[str]) -> int:
...Examples
Example 1:
ops = ["5", "2", "C", "D", "+"]Process step by step:
| Operation | Scores | Explanation |
|---|---|---|
"5" | [5] | Record 5 |
"2" | [5, 2] | Record 2 |
"C" | [5] | Remove 2 |
"D" | [5, 10] | Double previous score 5 |
"+" | [5, 10, 15] | Sum of 5 and 10 |
Final sum:
5 + 10 + 15 = 30Answer:
30Example 2:
ops = ["5","-2","4","C","D","9","+","+"]Process:
| Operation | Scores |
|---|---|
"5" | [5] |
"-2" | [5, -2] |
"4" | [5, -2, 4] |
"C" | [5, -2] |
"D" | [5, -2, -4] |
"9" | [5, -2, -4, 9] |
"+" | [5, -2, -4, 9, 5] |
"+" | [5, -2, -4, 9, 5, 14] |
Final sum:
5 - 2 - 4 + 9 + 5 + 14 = 27Answer:
27First Thought: Maintain All Valid Scores
Every operation only depends on recent valid scores.
For example:
| Operation | Depends on |
|---|---|
"D" | Previous valid score |
"+" | Previous two valid scores |
"C" | Most recent valid score |
This naturally suggests a stack.
The stack stores all current valid scores in order.
The top of the stack is the most recent valid score.
Key Insight
The operations exactly match stack behavior.
| Operation | Stack action |
|---|---|
| Integer | Push value |
"C" | Pop top |
"D" | Push 2 * top |
"+" | Push top + second_top |
Because each operation modifies only the most recent scores, we never need random access to older elements.
A stack gives efficient push and pop operations.
Algorithm
Create an empty stack called scores.
Process each operation op in ops.
If op is an integer:
scores.append(int(op))If op == "C":
scores.pop()If op == "D":
scores.append(scores[-1] * 2)If op == "+":
scores.append(scores[-1] + scores[-2])After processing all operations:
return sum(scores)Walkthrough
Consider:
ops = ["5", "2", "C", "D", "+"]Start:
scores = []Operation "5":
scores = [5]Operation "2":
scores = [5, 2]Operation "C" removes the most recent score:
scores = [5]Operation "D" doubles the previous score:
5 * 2 = 10Now:
scores = [5, 10]Operation "+" sums the last two scores:
5 + 10 = 15Now:
scores = [5, 10, 15]Final result:
5 + 10 + 15 = 30Correctness
The algorithm maintains the invariant that scores always contains exactly the valid scores after processing the current prefix of operations.
Initially, before processing any operation, the list is empty, which is correct.
If the next operation is an integer x, the rules say to record a new score x. The algorithm appends x to the stack, so the stack remains correct.
If the next operation is "C", the rules say to invalidate the previous valid score. The most recent valid score is the top of the stack, so removing it with pop() produces the correct remaining scores.
If the next operation is "D", the rules say to record double the previous valid score. The previous valid score is the top of the stack, so appending 2 * scores[-1] records the correct new score.
If the next operation is "+", the rules say to record the sum of the previous two valid scores. Those are the top two stack elements, so appending their sum records the correct new score.
Thus, after every operation, the stack exactly matches the valid scores defined by the problem statement.
At the end, the required answer is the total of all valid scores, which the algorithm returns using sum(scores).
Therefore, the algorithm is correct.
Complexity
Let n be the number of operations.
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | Each operation is processed once |
| Space | O(n) | The stack may contain all recorded scores |
Each stack operation is constant time.
Implementation
class Solution:
def calPoints(self, ops: list[str]) -> int:
scores = []
for op in ops:
if op == "C":
scores.pop()
elif op == "D":
scores.append(scores[-1] * 2)
elif op == "+":
scores.append(scores[-1] + scores[-2])
else:
scores.append(int(op))
return sum(scores)Code Explanation
We store valid scores in:
scores = []For each operation:
for op in ops:If the operation is "C":
scores.pop()we remove the most recent valid score.
If the operation is "D":
scores.append(scores[-1] * 2)we double the previous valid score and record it.
If the operation is "+":
scores.append(scores[-1] + scores[-2])we add the last two valid scores.
Otherwise, the operation is an integer string:
scores.append(int(op))Finally:
return sum(scores)returns the total score.
Testing
def run_tests():
s = Solution()
assert s.calPoints(["5", "2", "C", "D", "+"]) == 30
assert s.calPoints(["5","-2","4","C","D","9","+","+"]) == 27
assert s.calPoints(["1"]) == 1
assert s.calPoints(["1", "D"]) == 3
assert s.calPoints(["1", "2", "+"]) == 6
assert s.calPoints(["10", "-5", "D"]) == -5
assert s.calPoints(["3", "C", "4"]) == 4
print("all tests passed")
run_tests()Test meaning:
| Test | Expected | Why |
|---|---|---|
["5", "2", "C", "D", "+"] | 30 | Official example |
["5","-2","4","C","D","9","+","+"] | 27 | Official example with negative values |
["1"] | 1 | Single score |
["1", "D"] | 3 | Doubling operation |
["1", "2", "+"] | 6 | Sum of previous two scores |
["10", "-5", "D"] | -5 | Double negative value |
["3", "C", "4"] | 4 | Cancellation removes previous score |