title: Broken Calculator slug: broken-calculator difficulty: medium leetcode_id: 991 leetcode_url: https://leetcode.com/problems/broken-calculator/ categories: - math patterns: - greedy 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`   **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   **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)   **Step 4: Return the total** - Return the accumulated operation count   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.