title: Broken Calculator
slug: broken-calculator
difficulty: medium
leetcode_id: 991
leetcode_url: https://leetcode.com/problems/broken-calculator/
categories:
  - math
patterns:
  - slug: greedy
    is_optimal: true

function_signature: "def broken_calc(start_value: int, target: int) -> int:"

test_cases:
  visible:
    - input: { start_value: 2, target: 3 }
      expected: 2
    - input: { start_value: 5, target: 8 }
      expected: 2
    - input: { start_value: 3, target: 10 }
      expected: 3
  hidden:
    - input: { start_value: 1, target: 1 }
      expected: 0
    - input: { start_value: 10, target: 5 }
      expected: 5
    - input: { start_value: 1, target: 1000000000 }
      expected: 39
    - input: { start_value: 1, target: 2 }
      expected: 1
    - input: { start_value: 1, target: 1024 }
      expected: 10
    - input: { start_value: 100, target: 1 }
      expected: 99
    - input: { start_value: 1, target: 17 }
      expected: 6

description: |
  There is a broken calculator that has the integer `startValue` on its display initially. In one operation, you can:

  - Multiply the number on display by `2`, or
  - Subtract `1` from the number on display.

  Given two integers `startValue` and `target`, return *the minimum number of operations needed to display* `target` *on the calculator*.

constraints: |
  - `1 <= startValue, target <= 10^9`

examples:
  - input: "startValue = 2, target = 3"
    output: "2"
    explanation: "Use double operation and then decrement operation {2 -> 4 -> 3}."
  - input: "startValue = 5, target = 8"
    output: "2"
    explanation: "Use decrement and then double {5 -> 4 -> 8}."
  - input: "startValue = 3, target = 10"
    output: "3"
    explanation: "Use double, decrement and double {3 -> 6 -> 5 -> 10}."

explanation:
  intuition: |
    The forward direction (from `startValue` to `target`) involves choices that are hard to evaluate: should you double now or subtract first? The decision tree branches exponentially.

    The key insight is to **think backwards**: work from `target` back to `startValue`. In reverse, the operations become:

    - Divide by `2` (inverse of multiply)
    - Add `1` (inverse of subtract)

    Why does this help? Because now there's a **greedy rule**: if `target` is even, we *must* divide by 2 (it's always better than adding 1 multiple times). If `target` is odd, we have no choice but to add 1 first to make it even.

    Think of it like this: imagine you're trying to reduce a number to a smaller one. Dividing by 2 is a powerful operation that halves the distance quickly. Adding 1 is weak but necessary when division isn't possible. By always choosing division when available, we minimise the total operations.

  approach: |
    We solve this using a **Reverse Greedy Approach**:

    **Step 1: Handle the trivial case**

    - If `startValue >= target`, we can only subtract, so return `startValue - target`

    &nbsp;

    **Step 2: Work backwards from target**

    - While `target > startValue`:
      - If `target` is even: divide by 2 (one operation)
      - If `target` is odd: add 1 to make it even (one operation)
      - Increment the operation counter

    &nbsp;

    **Step 3: Add remaining difference**

    - After the loop, `target <= startValue`
    - Add `startValue - target` to the operation count (these are the subtractions needed in the forward direction, or additions in reverse)

    &nbsp;

    **Step 4: Return the total**

    - Return the accumulated operation count

    &nbsp;

    This greedy approach is optimal because dividing by 2 is always the most efficient way to reduce a number, and we only add 1 when forced to by odd numbers.

  common_pitfalls:
    - title: Forward Simulation Trap
      description: |
        A tempting approach is to simulate forward from `startValue` to `target`, using BFS or recursion to explore all possible paths. This leads to exponential time complexity.

        With constraints up to `10^9`, this approach will cause **Time Limit Exceeded** or **Memory Limit Exceeded**. The state space is too large to explore exhaustively.
      wrong_approach: "BFS or recursion exploring all forward paths"
      correct_approach: "Work backwards with greedy decisions"

    - title: Forgetting the Base Case
      description: |
        When `startValue >= target`, you might try to apply the backward algorithm, but division doesn't help when you're already above the target.

        For example, `startValue = 10, target = 5`: the answer is simply `10 - 5 = 5` subtractions. No multiplication/division is useful here because multiplying only takes you further from the target.
      wrong_approach: "Applying the same logic regardless of start vs target"
      correct_approach: "Handle startValue >= target as a special case"

    - title: Integer Overflow Concerns
      description: |
        When working backwards, you only divide and add 1, so values decrease or increase by 1. There's no overflow risk.

        However, if you tried a forward approach with multiplication, values could overflow quickly. This is another reason the backward approach is superior.

  key_takeaways:
    - "**Reverse thinking**: When forward decisions are complex, try working backwards where the choices may be clearer"
    - "**Greedy with proof**: The greedy choice (always divide when even) is provably optimal because division is strictly more powerful than addition"
    - "**Constraint awareness**: With `10^9` constraints, any exponential or even O(n) simulation would be too slow; we need O(log n)"
    - "**Related problems**: This pattern of reversing operations appears in problems like *Reaching Points* and other math/greedy puzzles"

  time_complexity: "O(log(target)). Each division halves the target, so we perform at most O(log(target)) divisions. The additions between divisions are bounded."
  space_complexity: "O(1). We only use a few integer variables regardless of input size."

solutions:
  - approach_name: Reverse Greedy
    is_optimal: true
    code: |
      def broken_calc(start_value: int, target: int) -> int:
          # If we're already at or above target, just subtract
          if start_value >= target:
              return start_value - target

          operations = 0

          # Work backwards from target to start_value
          while target > start_value:
              if target % 2 == 0:
                  # Even: divide by 2 (reverse of multiply)
                  target //= 2
              else:
                  # Odd: add 1 (reverse of subtract)
                  target += 1
              operations += 1

          # Add the remaining difference (subtractions in forward direction)
          return operations + (start_value - target)
    explanation: |
      **Time Complexity:** O(log(target)) — Each division halves the value, giving logarithmic iterations.

      **Space Complexity:** O(1) — Only a few integer variables used.

      By working backwards, we transform a complex decision problem into a simple greedy one. When target is even, dividing is always optimal. When odd, we must add 1 first. This guarantees the minimum operations.

  - approach_name: BFS (Suboptimal)
    is_optimal: false
    code: |
      from collections import deque

      def broken_calc(start_value: int, target: int) -> int:
          # BFS from start_value to target
          # WARNING: This is too slow for large inputs!
          if start_value >= target:
              return start_value - target

          queue = deque([(start_value, 0)])  # (current_value, operations)
          visited = {start_value}

          while queue:
              current, ops = queue.popleft()

              # Try multiply by 2
              doubled = current * 2
              if doubled == target:
                  return ops + 1
              if doubled < target * 2 and doubled not in visited:
                  visited.add(doubled)
                  queue.append((doubled, ops + 1))

              # Try subtract 1
              decremented = current - 1
              if decremented == target:
                  return ops + 1
              if decremented > 0 and decremented not in visited:
                  visited.add(decremented)
                  queue.append((decremented, ops + 1))

          return -1  # Should never reach here
    explanation: |
      **Time Complexity:** O(2^n) in worst case — Exponential state space exploration.

      **Space Complexity:** O(2^n) — Storing visited states.

      This BFS approach explores all possible paths forward. While conceptually correct, it's far too slow for the given constraints (`target` up to `10^9`). Included to illustrate why the greedy reverse approach is necessary.
