Burst Balloons

Burst Balloons is a famous hard interview question. It traps almost everyone the first time. The natural instinct is to ask “which balloon should I burst first?” That instinct is exactly the wrong one. The secret is to flip the question and ask which balloon you burst last. Once you see that flip, a tangled problem turns into a clean table.

🎯 The Problem

You have a row of balloons, each with a number painted on it. You burst them one by one to earn coins. Your job is to burst all balloons in the best order to earn the most coins total.

  • Bursting a balloon earns left * self * right coins.
  • That is the balloon’s number times its left neighbor times its right neighbor.
  • When a balloon bursts, its neighbors become next to each other. So the neighbors change as you go.
  • If a neighbor is missing, because you burst it or it is off the edge, treat that side as a balloon with the number 1.
Input: nums = [3, 1, 5, 8]
Output: 167
Explanation: a best order is
burst 1 -> 3 * 1 * 5 = 15, row becomes [3, 5, 8]
burst 5 -> 3 * 5 * 8 = 120, row becomes [3, 8]
burst 3 -> 1 * 3 * 8 = 24, row becomes [8]
burst 8 -> 1 * 8 * 1 = 8, row becomes []
total = 15 + 120 + 24 + 8 = 167

We pad both ends with a 1. So the real working row is [1, 3, 1, 5, 8, 1]. The two extra 1s are the imaginary balloons off the edges. They make the coin math the same everywhere.

Here is the picture of the question. A row of balloons, each burst earning left times self times right.

edge = 1

3

1

5

8

edge = 1

goal: burst all, maximize coins

🐢 Approach 1: Try Every Order (Brute Force)

The idea in one line: try every possible order of bursting and keep the best total.

The idea:

  • Pick a balloon to burst first. Earn its coins.
  • Solve the smaller row that is left. Try that for every first choice.

How it works:

  • For each balloon, burst it first, then recurse on what remains.
  • Keep the largest total over all first choices.

Why it is weak:

  • For n balloons there are about n! orders, which is n factorial.
  • That is n times n-1 times n-2 down to 1. Even ten balloons becomes millions of orders.
  • The neighbors keep changing, so bursting a middle balloon shifts coins far away.
  • The sub-problems overlap in a messy way, so you cannot reuse them.

Here is the try-every-burst-order code:

burst_balloons_brute_force.py
def max_coins(nums):
def dfs(arr):
if not arr:
return 0
best = 0
for i in range(len(arr)):
left = arr[i - 1] if i > 0 else 1
right = arr[i + 1] if i + 1 < len(arr) else 1
best = max(best, left * arr[i] * right + dfs(arr[:i] + arr[i + 1:]))
return best
return dfs(nums)

⚡ Approach 2: Last Balloon Insight Plus Memoization (Better)

The idea in one line: instead of which balloon to burst first, ask which to burst last in a range.

The idea:

  • Look at balloons between two edges, left and right, not counting the edges.
  • Say balloon k is the very last one burst in that range.

Why it works:

  • By the time you burst k, every other balloon in the range is gone.
  • So k’s neighbors are exactly left and right. They do not move.
  • The coins for bursting k last are fixed at nums[left] * nums[k] * nums[right].
  • Picking the last balloon freezes its neighbors and splits the range into two independent pieces.

How it works:

  • Try every k as the last balloon and keep the best.
  • The left piece and the right piece never touch, because k is the wall between them.
  • Store each range’s answer in a table so you never redo it. That storing is memoization.

Here is the memoized interval code:

burst_balloons_memo.py
from functools import lru_cache
def max_coins(nums):
nums = [1] + nums + [1]
@lru_cache(None)
def dfs(left, right):
if left + 1 == right:
return 0
best = 0
for last in range(left + 1, right):
coins = nums[left] * nums[last] * nums[right]
best = max(best, coins + dfs(left, last) + dfs(last, right))
return best
return dfs(0, len(nums) - 1)

🚀 Approach 3: Bottom-Up Interval DP (Best)

The idea in one line: build the same answer from the bottom up, filling a table of ranges.

The idea:

  • Each cell stands for a range, also called an interval, of the row. This is interval DP.
  • Pad the row with a 1 on each side.
  • dp[left][right] holds the most coins from bursting every balloon strictly between left and right.
  • The edges are not burst. They are only used as multipliers.

How it works:

  • Fill the table by range length, from small ranges to big ones.
  • For each range, try every balloon k strictly inside as the last one to burst.
  • Value for that choice: dp[left][k] + nums[left] * nums[k] * nums[right] + dp[k][right].
  • The first part is the best from the left sub-range. The middle is k burst last. The last is the right sub-range.
  • Keep the largest value over all k.

Why it is best:

  • Long ranges reuse the answers of their shorter sub-ranges.
  • No recursion stack, just a clean table filled in order.
  • Three nested loops give O(n³) time, far better than factorial.

Here is a dry run of the ranges we build, smallest first, growing up to the full row.

pad row: 1 3 1 5 8 1

length 1 ranges: each single balloon

length 2 ranges: pairs of balloons

longer ranges combine sub-answers

full range dp[0][5] tries each k as last

answer = 167

Steps to Solve

  1. Make a new array that is nums with a 1 added at the front and a 1 added at the back.
  2. Let N be the length of this padded array. Make a grid dp of size N by N, all zeros.
  3. Loop over range length from 2 up, so the range has at least one balloon inside.
  4. For each left, set right = left + length.
  5. For each k strictly between left and right, compute dp[left][k] + nums[left]*nums[k]*nums[right] + dp[k][right].
  6. Store the largest such value in dp[left][right].
  7. The answer is dp[0][N-1], the full range from edge to edge.

This Python version pads the list and fills the table with two range loops.

burst_balloons.py
def max_coins(nums):
pad = [1] + nums + [1] # add edge balloons of value 1
N = len(pad)
dp = [[0] * N for _ in range(N)] # dp[left][right] over the range
for length in range(2, N): # range length
for left in range(0, N - length):
right = left + length
best = 0
for k in range(left + 1, right): # k is burst last
coins = (pad[left] * pad[k] * pad[right]
+ dp[left][k] + dp[k][right])
if coins > best:
best = coins
dp[left][right] = best
return dp[0][N - 1]
nums = [3, 1, 5, 8]
print(max_coins(nums))

The output of the above code will be:

167

Let us walk through the Python version line by line. Code first, then why each line is there.

pad = [1] + nums + [1]
N = len(pad)
dp = [[0] * N for _ in range(N)]

We wrap the row with a 1 on each side. Those edge 1s are the imaginary balloons off the ends, so the coin formula never has to check for missing neighbors. Then we make an N by N table full of zeros. The cell dp[left][right] will hold the best coins for the balloons strictly between left and right.

for length in range(2, N):
for left in range(0, N - length):
right = left + length

We grow the ranges by length. We start at length = 2 because a range needs at least one balloon strictly inside it, and a gap of 2 between left and right leaves exactly one slot. Filling short ranges before long ones matters, because a long range reuses the answers of its shorter sub-ranges. right is just left plus the length.

best = 0
for k in range(left + 1, right):
coins = (pad[left] * pad[k] * pad[right]
+ dp[left][k] + dp[k][right])
if coins > best:
best = coins

This inner loop is the heart of the trick. We try every balloon k inside the range as the last one to burst. Because k bursts last, the only balloons left beside it are left and right. So its coins are pad[left] * pad[k] * pad[right], fixed and clean. Then we add dp[left][k], the best from the sub-range to its left, and dp[k][right], the best from the sub-range to its right. We keep the largest total over all choices of k.

dp[left][right] = best
return dp[0][N - 1]

We store the best for this range. After all ranges are filled, dp[0][N-1] covers the full row from one edge to the other. That cell is the final answer, which is 167 for this input.

⏱️ Time and Space Complexity

Trying every order is factorial time, which is hopeless past a handful of balloons. The interval DP has three nested loops. One for range length, one for the left edge, and one for the last balloon k. That is O(n³) time, where n is the number of balloons. The table of ranges takes O(n²) space. For interview sizes O(n³) is perfectly fine, and it is a massive improvement over factorial.

Approach Time Complexity Space Complexity
Try every burst order O(n!) O(n)
Top-down memoization O(n³) O(n²)
Bottom-up interval DP O(n³) O(n²)

Tip

The whole problem cracks open the moment you say “let k be the last balloon I burst”. Practice saying that sentence out loud. If the interviewer sees you reach for the last-burst idea, they know you understand interval DP, not just the formula.

🧩 Key Takeaways

  • ✅ Ask which balloon bursts last in a range, not which bursts first.
  • ✅ The last balloon’s neighbors are frozen at the range edges, so its coins are fixed.
  • ✅ Pad the row with a 1 on each side so the coin math never hits a missing neighbor.
  • ✅ Fill the table from short ranges to long ranges, because long ranges reuse short ones.
  • ✅ Three nested loops give O(n³) time, far better than the factorial brute force.

Check Your Knowledge

4 questions Show quiz Hide quiz

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What is the key insight that makes Burst Balloons solvable with DP?

    Why: Choosing the last balloon to burst fixes its neighbors at the range edges and splits the range cleanly.

  2. 2

    Why do we pad the array with a 1 on each side?

    Why: The edge 1s act as imaginary neighbors so left times self times right works everywhere.

  3. 3

    What does dp[left][right] represent?

    Why: Each cell is the best coin total for the open range of balloons between the two edges.

  4. 4

    What is the time complexity of the interval DP solution?

    Why: Three nested loops over range length, left edge, and the last balloon k give O(n³).

🚀 What’s Next?