A clear explanation of Burst Balloons using interval dynamic programming and the last-burst idea.
Problem Restatement
We are given an integer array nums.
Each value represents a balloon.
When we burst balloon i, we gain coins equal to:
nums[i - 1] * nums[i] * nums[i + 1]If the left or right neighbor is outside the array, treat that missing neighbor as 1.
After a balloon is burst, it disappears, so its left and right neighbors change.
Return the maximum coins we can collect by choosing the best bursting order.
The official constraints are 1 <= nums.length <= 300 and 0 <= nums[i] <= 100. The examples include nums = [3,1,5,8], whose answer is 167.
Input and Output
| Item | Meaning |
|---|---|
| Input | Integer array nums |
| Output | Maximum coins collectable |
| Action | Burst one balloon at a time |
| Important detail | Neighbors change after each burst |
| Boundary rule | Missing neighbor counts as 1 |
Function shape:
def maxCoins(nums: list[int]) -> int:
...Examples
Example 1:
nums = [3, 1, 5, 8]One optimal order is:
[3,1,5,8] -> [3,5,8] -> [3,8] -> [8] -> []Coins:
3*1*5 + 3*5*8 + 1*3*8 + 1*8*1
= 15 + 120 + 24 + 8
= 167Output:
167Example 2:
nums = [1, 5]Burst 1 first:
1*1*5 = 5Then burst 5:
1*5*1 = 5Total:
10Output:
10First Thought: Try Every Burst Order
A direct solution tries every possible order.
For n balloons, there are many orders.
At the first step we can choose any of n balloons.
At the next step, any of n - 1 remaining balloons.
So this becomes roughly:
n!This is far too large for n <= 300.
We need dynamic programming.
Why This Problem Feels Hard
If we think about the first balloon to burst, the subproblems are messy.
Suppose we burst one balloon first. Its two neighbors become adjacent. That changes future coin values.
So choosing the first burst does not cleanly split the array.
The trick is to think in reverse:
Choose the last balloon to burst inside an interval.
When a balloon is the last one left inside an interval, its two outside neighbors are fixed.
That makes the coin calculation stable.
Add Virtual Boundary Balloons
Add 1 to both ends:
arr = [1] + nums + [1]For example:
nums = [3, 1, 5, 8]
arr = [1, 3, 1, 5, 8, 1]These boundary balloons are never burst.
They only help compute coins at the edges.
DP Definition
Let:
dp[left][right]mean:
Maximum coins we can get by bursting all balloons strictly between
leftandright.
So left and right are boundary balloons for the current interval.
They are not burst in this subproblem.
For example:
dp[0][5]means:
Maximum coins from bursting all original balloons between the two virtual boundaries.
That is the final answer.
Last Balloon Choice
For interval (left, right), choose some balloon mid as the last balloon to burst inside this interval.
Before bursting mid, all balloons between left and mid are gone, and all balloons between mid and right are gone.
So at that moment, the neighbors of mid are exactly:
arr[left]and:
arr[right]Coins from bursting mid last:
arr[left] * arr[mid] * arr[right]The left side contributes:
dp[left][mid]The right side contributes:
dp[mid][right]So the transition is:
dp[left][right] = max(
dp[left][mid] + arr[left] * arr[mid] * arr[right] + dp[mid][right]
)for every mid between left and right.
Algorithm
- Build
arr = [1] + nums + [1]. - Create a 2D DP table filled with zeroes.
- Process intervals by increasing length.
- For each interval
(left, right):- Try every
midbetween them. - Treat
midas the last balloon burst in this interval. - Update
dp[left][right].
- Try every
- Return
dp[0][n + 1].
Intervals with no balloon inside have value 0.
That means when:
right == left + 1there is nothing to burst.
Correctness
For any interval (left, right), consider an optimal way to burst all balloons inside it.
Among those balloons, one balloon must be burst last. Call it mid.
When mid is burst last, every other balloon inside (left, right) has already disappeared. Therefore, its current neighbors are exactly left and right.
So the coins from the final burst are:
arr[left] * arr[mid] * arr[right]All balloons between left and mid must have been burst before that final step. The best possible coins from them are dp[left][mid].
All balloons between mid and right must also have been burst before that final step. The best possible coins from them are dp[mid][right].
Thus every optimal solution for (left, right) has value:
dp[left][mid] + arr[left] * arr[mid] * arr[right] + dp[mid][right]for some valid mid.
The algorithm tries every possible mid, so it considers the last balloon used by the optimal solution.
Since smaller intervals are computed before larger intervals, both subproblem values are already correct when needed.
Therefore, dp[left][right] is correct for every interval, and dp[0][n + 1] is the maximum coins for the whole array.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n^3) | There are O(n^2) intervals and up to O(n) choices of mid |
| Space | O(n^2) | The DP table stores every interval |
This fits n <= 300.
Implementation
class Solution:
def maxCoins(self, nums: list[int]) -> int:
arr = [1] + nums + [1]
n = len(arr)
dp = [[0] * n for _ in range(n)]
for length in range(2, n):
for left in range(0, n - length):
right = left + length
best = 0
for mid in range(left + 1, right):
coins = (
dp[left][mid]
+ arr[left] * arr[mid] * arr[right]
+ dp[mid][right]
)
best = max(best, coins)
dp[left][right] = best
return dp[0][n - 1]Code Explanation
We add virtual boundary balloons:
arr = [1] + nums + [1]They make every burst have a left and right neighbor.
Create the DP table:
dp = [[0] * n for _ in range(n)]dp[left][right] stores the best result for the open interval (left, right).
We process by interval length:
for length in range(2, n):Length 2 means there is exactly one balloon between left and right.
Choose the interval:
for left in range(0, n - length):
right = left + lengthTry each possible last balloon:
for mid in range(left + 1, right):Compute the total if mid is burst last:
coins = (
dp[left][mid]
+ arr[left] * arr[mid] * arr[right]
+ dp[mid][right]
)Keep the best value:
best = max(best, coins)Store it:
dp[left][right] = bestThe final interval is between the two virtual boundaries:
return dp[0][n - 1]Testing
def run_tests():
s = Solution()
assert s.maxCoins([3, 1, 5, 8]) == 167
assert s.maxCoins([1, 5]) == 10
assert s.maxCoins([1]) == 1
assert s.maxCoins([0]) == 0
assert s.maxCoins([0, 1, 0]) == 1
assert s.maxCoins([9, 76, 64, 21]) == 116718
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
[3, 1, 5, 8] | Standard example |
[1, 5] | Small array with two choices |
[1] | Single balloon |
[0] | Zero-valued balloon |
[0, 1, 0] | Zeroes around a positive value |
[9, 76, 64, 21] | Larger values and interval interactions |