Burst Balloons
Hard · 2-D Dynamic Programming · Explanation and code example
Dynamic Programming (Bottom-Up)
How it works
We want the maximum coins we can get by bursting balloons in the best order. The key trick is to think in reverse:
Instead of choosing which balloon to burst first, we choose which balloon is burst last in a range.
If we are only looking at balloons between l and r, and balloon i is the last one burst in that range, then:
- all balloons inside
(l..r)exceptiare already gone - so the neighbors of
iare fixed asnew_nums[l - 1]andnew_nums[r + 1] - coins gained from bursting
ilast are:new_nums[l - 1] * new_nums[i] * new_nums[r + 1]
After bursting i last, the remaining problem splits into two independent subproblems:
- best coins for
[l .. i-1] - best coins for
[i+1 .. r]
This makes the problem perfect for interval DP.
Steps
- Create a new array
new_nums = [1] + nums + [1]to handle boundaries easily. - Let
dp[l][r]represent the maximum coins we can collect by bursting balloons from indexltor(insidenew_nums). - Initialize
dpwith zeros since empty ranges give0coins. - Fill the DP table by increasing interval size:
- iterate
lfromndown to1 - iterate
rfromlup ton
- iterate
- For each interval
[l, r], try every ballooniin[l, r]as the last balloon to burst:- coins from bursting
ilast:new_nums[l - 1] * new_nums[i] * new_nums[r + 1]
- plus the best coins from the left sub-interval:
dp[l][i - 1]
- plus the best coins from the right sub-interval:
dp[i + 1][r]
- take the maximum over all
i
- coins from bursting
- The final answer is stored in
dp[1][n], representing bursting all original balloons.
Complexity
- Time complexity:
O(n^3) - Space complexity:
O(n^2)
Things to watch for
Thinking About First Balloon to Burst Instead of Last
The key insight is to consider which balloon is burst LAST in a range, not first. When thinking about the first balloon, the subproblems are not independent because neighbors change. When considering the last balloon, the boundary neighbors are fixed.
Forgetting to Pad the Array with 1s
The problem states that out-of-bounds neighbors are treated as value 1. Forgetting to add 1 at both ends of the array causes index errors or incorrect coin calculations at the boundaries.
Incorrect Loop Order in Bottom-Up DP
In bottom-up DP, smaller intervals must be computed before larger ones. The left index l should iterate in decreasing order while r iterates in increasing order. Wrong iteration order reads uncomputed DP values.
Using Wrong Neighbors in Coin Calculation
When balloon i is the last to burst in range [l, r], its neighbors are nums[l-1] and nums[r+1] (the boundaries), not nums[i-1] and nums[i+1] (adjacent indices).
Not Handling Empty Range Base Case
When l > r, there are no balloons to burst, so the result should be 0. Missing this base case causes incorrect recursion or array index errors.
Python code example
class Solution:
def maxCoins(self, nums):
n = len(nums)
new_nums = [1] + nums + [1]
dp = [[0] * (n + 2) for _ in range(n + 2)]
for l in range(n, 0, -1):
for r in range(l, n + 1):
for i in range(l, r + 1):
coins = new_nums[l - 1] * new_nums[i] * new_nums[r + 1]
coins += dp[l][i - 1] + dp[i + 1][r]
dp[l][r] = max(dp[l][r], coins)
return dp[1][n]
C++ code example
class Solution {
public:
int maxCoins(vector<int>& nums) {
int n = nums.size();
vector<int> newNums(n + 2, 1);
for (int i = 0; i < n; i++) {
newNums[i + 1] = nums[i];
}
vector<vector<int>> dp(n + 2, vector<int>(n + 2, 0));
for (int l = n; l >= 1; l--) {
for (int r = l; r <= n; r++) {
for (int i = l; i <= r; i++) {
int coins = newNums[l - 1] * newNums[i] * newNums[r + 1];
coins += dp[l][i - 1] + dp[i + 1][r];
dp[l][r] = max(dp[l][r], coins);
}
}
}
return dp[1][n];
}
};
Source and license
Adapted from NeetCode’s source · MIT license. Node types are supplied by LeetCode where needed.