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

    &nbsp;

    **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

    &nbsp;

    **Step 3: Return the sum**

    - Return `sum(record)` — the sum of all remaining valid scores

    &nbsp;

    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.
