312. Burst Balloons - Explanation
Description
You are given an array of integers nums of size n. The ith element represents a balloon with an integer value of nums[i]. You must burst all of the balloons.
If you burst the ith balloon, you will receive nums[i - 1] * nums[i] * nums[i + 1] coins. If i - 1 or i + 1 goes out of bounds of the array, then assume the out of bounds value is 1.
Return the maximum number of coins you can receive by bursting all of the balloons.
Example 1:
Input: nums = [4,2,3,7]
Output: 143
Explanation:
nums = [4,2,3,7] --> [4,3,7] --> [4,7] --> [7] --> []
coins = 4*2*3 + 4*3*7 + 1*4*7 + 1*7*1 = 143Constraints:
n == nums.length1 <= n <= 3000 <= nums[i] <= 100
Topics
Recommended Time & Space Complexity
You should aim for a solution with O(n ^ 3) time and O(n ^ 2) space, where n is the size of the input array.
Hint 1
Try to simulate the process recursively by passing the array to the recursive function. At each step, iterate through the array, pop an element, and recursively apply the same process to the two subarrays on both sides of the popped element, returning the maximum result from all recursive paths. This approach is exponential. Can you think of a way to optimize it? Maybe you should consider observing the subproblems instead of modifying the array.
Hint 2
Instead of passing the array, we can pass the range of indices l and r that need to be processed. We pad the input array with 1s on both sides for easier computation, but l and r represent the first and last indices of the original input array. Can you think of a reverse engineering approach for popping elements?
Hint 3
We determine the result by considering each element as the last one to be popped in the current range. For each element, we calculate its value by multiplying it with the elements at l - 1 and r + 1, then recursively solve the subproblems for the ranges (l, i - 1) and (i + 1, r), where i is the current element in the given range. Can you think of a way to optimize and avoid redundant calculations?
Hint 4
We can use memoization to cache the results of recursive calls and avoid redundant calculations. A hash map or a 2D array can be used to store results since the recursive function parameters l and r are within the range of the input array size.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Recursion - Breaking down problems into smaller subproblems
- Dynamic Programming - Memoization and tabulation techniques for optimization
- Interval DP - Solving problems by considering all possible subranges and combining results
- Array Manipulation - Padding arrays with boundary values to simplify edge cases
1. Brute Force (Recursion)
Intuition
This problem asks us to find the maximum coins we can collect by bursting balloons in the best possible order.
When we burst a balloon, the number of coins we gain depends on its current neighbors. Since bursting one balloon changes the neighbors of others, the order of bursting matters a lot.
A brute-force way to solve this is to try every possible balloon as the last one to burst in the current array:
- If we choose a balloon to burst now, we earn coins based on its left and right neighbors.
- Then the problem reduces to bursting the remaining balloons.
The recursive function represents:
“What is the maximum number of coins we can collect from the current list of balloons?”
To simplify edge cases, we add 1 to both ends of the array so every balloon always has two neighbors.
Algorithm
- Add
1to the beginning and end of the array to handle boundaries safely. - Define a recursive function
dfs(nums):numsrepresents the current list of remaining balloons
- If only the two boundary
1s are left:- Return
0since no real balloons remain
- Return
- Initialize
maxCoins = 0 - For each balloon index
ibetween the boundaries:- Calculate coins gained by bursting
nums[i]:nums[i - 1] * nums[i] * nums[i + 1]
- Recursively compute the maximum coins from the remaining balloons:
- Remove
nums[i]and calldfson the new list
- Remove
- Update
maxCoinswith the best result
- Calculate coins gained by bursting
- Return
maxCoinsfor the current configuration - Start the recursion with the full padded array and return the result
class Solution:
def maxCoins(self, nums: List[int]) -> int:
nums = [1] + nums + [1]
def dfs(nums):
if len(nums) == 2:
return 0
maxCoins = 0
for i in range(1, len(nums) - 1):
coins = nums[i - 1] * nums[i] * nums[i + 1]
coins += dfs(nums[:i] + nums[i + 1:])
maxCoins = max(maxCoins, coins)
return maxCoins
return dfs(nums)Time & Space Complexity
- Time complexity:
- Space complexity:
2. Dynamic Programming (Top-Down)
Intuition
The brute-force recursion tries every possible bursting order, which repeats the same work many times.
A useful way to think about this problem is:
instead of choosing the first balloon to burst, choose the last balloon to burst in a subarray.
Why this helps:
- If balloon
iis the last one to burst between indiceslandr, then at that moment:- everything inside
(l..r)exceptiis already gone - the neighbors of
iare fixed:nums[l - 1]on the left andnums[r + 1]on the right
- everything inside
- So the coins gained from bursting
ilast are:nums[l - 1] * nums[i] * nums[r + 1]
- And the remaining work splits cleanly into two independent parts:
- best coins from
(l..i-1) - best coins from
(i+1..r)
- best coins from
This creates overlapping subproblems, so we store results in a memo table dp keyed by (l, r).
Algorithm
- Add
1to both ends of the array to avoid boundary checks. - Use a memo table
dpwhere:dp[(l, r)]stores the maximum coins obtainable by bursting balloons in the range[l, r]
- Define a recursive function
dfs(l, r):- If
l > r, return0(no balloons to burst) - If
(l, r)is already computed, return it
- If
- Try every balloon
iin[l, r]as the last balloon to burst:- Coins from bursting
ilast:nums[l - 1] * nums[i] * nums[r + 1]
- Plus the best coins from the left side:
dfs(l, i - 1)
- Plus the best coins from the right side:
dfs(i + 1, r)
- Take the maximum over all choices of
i
- Coins from bursting
- Store the best value in
dp[(l, r)]and return it - The final answer is
dfs(1, len(nums) - 2)(the original balloons, excluding the added boundaries)
class Solution:
def maxCoins(self, nums: List[int]) -> int:
nums = [1] + nums + [1]
dp = {}
def dfs(l, r):
if l > r:
return 0
if (l, r) in dp:
return dp[(l, r)]
dp[(l, r)] = 0
for i in range(l, r + 1):
coins = nums[l - 1] * nums[i] * nums[r + 1]
coins += dfs(l, i - 1) + dfs(i + 1, r)
dp[(l, r)] = max(dp[(l, r)], coins)
return dp[(l, r)]
return dfs(1, len(nums) - 2)Time & Space Complexity
- Time complexity:
- Space complexity:
3. Dynamic Programming (Bottom-Up)
Intuition
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.
Algorithm
- 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.
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]Time & Space Complexity
- Time complexity:
- Space complexity:
Common Pitfalls
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.
# Wrong mental model: burst balloon i first
# The remaining array's structure changes unpredictably
# Correct: burst balloon i last in range [l, r]
# Neighbors of i are fixed as nums[l-1] and nums[r+1]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.
# Wrong: using original array
nums = [3, 1, 5, 8]
# Correct: pad with 1s
nums = [1] + nums + [1] # [1, 3, 1, 5, 8, 1]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).
# Wrong: using adjacent indices
coins = nums[i-1] * nums[i] * nums[i+1]
# Correct: using range boundaries
coins = nums[l-1] * nums[i] * nums[r+1]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.
Sign in to join the discussion