Burst Balloons
Problem Statement
You are given n balloons, indexed from 0 to n - 1. Each balloon is painted with a number on it represented by an array nums. You are asked to burst all the balloons.
If you burst the ith balloon, you will get nums[i - 1] * nums[i] * nums[i + 1] coins. If i - 1 or i + 1 goes out of bounds of the array, then treat it as if there is a balloon with a 1 painted on it.
Return the maximum coins you can collect by bursting the balloons wisely.
Input/Output Format
Input:
nums(List[int]): Array of balloon values
Output:
- (int): Maximum coins obtainable
Constraints
n == nums.length1 <= n <= 3000 <= nums[i] <= 100
Examples
Example 1: Standard Case
Input: nums = [3, 1, 5, 8]
Output: 167
Explanation:
Original: [3, 1, 5, 8] (with virtual 1s: [1, 3, 1, 5, 8, 1])
Optimal order (one possible):
1. Burst balloon 1 (value 1): coins = 3 * 1 * 5 = 15
Array becomes: [3, 5, 8]
2. Burst balloon 5: coins = 3 * 5 * 8 = 120
Array becomes: [3, 8]
3. Burst balloon 3: coins = 1 * 3 * 8 = 24
Array becomes: [8]
4. Burst balloon 8: coins = 1 * 8 * 1 = 8
Array becomes: []
Total: 15 + 120 + 24 + 8 = 167Example 2: Two Balloons
Input: nums = [1, 5]
Output: 10
Explanation:
With virtual 1s: [1, 1, 5, 1]
Option 1: Burst 1 first, then 5
- Burst 1: 1 * 1 * 5 = 5
- Burst 5: 1 * 5 * 1 = 5
- Total: 10
Option 2: Burst 5 first, then 1
- Burst 5: 1 * 5 * 1 = 5
- Burst 1: 1 * 1 * 1 = 1
- Total: 6
Maximum: 10Example 3: Single Element
Input: nums = [10]
Output: 10
Explanation: Only one balloon. Coins = 1 * 10 * 1 = 10.
Visual Explanation

ASCII Art: Thinking in Reverse
Key insight: Think of which balloon to burst LAST in a range!
nums = [3, 1, 5, 8] with virtual 1s becomes [1, 3, 1, 5, 8, 1]
^ ^
virtual virtual
If we burst balloon k LAST in range (i, j):
- All other balloons in (i, j) are already gone
- Neighbors of k are balloons i and j (boundary balloons)
- Coins from k = nums[i] * nums[k] * nums[j]
DP[i][j] = max coins from bursting all balloons between i and j
(excluding i and j, which are boundaries)
For range (0, 5) with k=3 (balloon with value 5):
- Left part: DP[0][3] = max coins bursting (3, 1)
- Right part: DP[3][5] = max coins bursting (8)
- Burst k: nums[0] * nums[3] * nums[5] = 1 * 5 * 1 = 5
- Total = DP[0][3] + DP[3][5] + 5Why "Burst Last" Works
Problem with "burst first" thinking:
- After bursting balloon i, its neighbors change
- This creates dependency on burst order
- Exponential number of states
"Burst last" insight:
- If balloon k is the LAST to be burst in range (i, j):
- Left side (i, k) is already empty
- Right side (k, j) is already empty
- When bursting k, its neighbors are EXACTLY i and j
- No dependency on intermediate burst order!Solution Code
Approach 1: Top-Down (Memoization)
from typing import List
from functools import lru_cache
def maxCoins(nums: List[int]) -> int:
"""
Find max coins using memoization with "burst last" strategy.
For each range, try each balloon as the last to burst.
Args:
nums: Array of balloon values
Returns:
Maximum coins obtainable
Time Complexity: O(n^3)
Space Complexity: O(n^2)
"""
# Add virtual balloons at boundaries
balloons = [1] + nums + [1]
n = len(balloons)
@lru_cache(maxsize=None)
def dp(left: int, right: int) -> int:
"""
Max coins from bursting all balloons between left and right
(exclusive of left and right boundaries).
"""
# No balloons between left and right
if left + 1 >= right:
return 0
max_coins = 0
# Try each balloon as the LAST to burst
for k in range(left + 1, right):
# Coins from bursting k last
coins = balloons[left] * balloons[k] * balloons[right]
# Add coins from left and right subproblems
total = dp(left, k) + coins + dp(k, right)
max_coins = max(max_coins, total)
return max_coins
return dp(0, n - 1)Complexity: Time O(n^3) · Space O(n^2)
- Time: There are O(n^2) subproblems (all pairs left, right) and each tries O(n) choices for which balloon to burst last.
- Space: The memoization cache stores O(n^2) entries plus O(n) recursion stack depth.
Approach 2: Bottom-Up (Tabulation)
from typing import List
def maxCoins(nums: List[int]) -> int:
"""
Find max coins using bottom-up DP.
Build solution for increasing range lengths.
Args:
nums: Array of balloon values
Returns:
Maximum coins obtainable
Time Complexity: O(n^3)
Space Complexity: O(n^2)
"""
# Add virtual balloons
balloons = [1] + nums + [1]
n = len(balloons)
# dp[i][j] = max coins from bursting balloons between i and j
dp = [[0] * n for _ in range(n)]
# Iterate by range length
for length in range(2, n): # Minimum length 2 to have balloons between
for left in range(n - length):
right = left + length
# Try each balloon as last to burst
for k in range(left + 1, right):
coins = balloons[left] * balloons[k] * balloons[right]
dp[left][right] = max(
dp[left][right],
dp[left][k] + coins + dp[k][right]
)
return dp[0][n - 1]public int maxCoins(int[] nums) {
int orig = nums.length;
// Add virtual balloons at boundaries
int[] balloons = new int[orig + 2];
balloons[0] = 1;
balloons[orig + 1] = 1;
for (int i = 0; i < orig; i++) balloons[i + 1] = nums[i];
int n = balloons.length;
// dp[i][j] = max coins from bursting balloons between i and j (exclusive)
int[][] dp = new int[n][n];
// Iterate by range length
for (int length = 2; length < n; length++) {
for (int left = 0; left < n - length; left++) {
int right = left + length;
// Try each balloon as last to burst
for (int k = left + 1; k < right; k++) {
int coins = balloons[left] * balloons[k] * balloons[right];
dp[left][right] = Math.max(
dp[left][right],
dp[left][k] + coins + dp[k][right]
);
}
}
}
return dp[0][n - 1];
}Complexity: Time O(n^3) · Space O(n^2)
- Time: Three nested loops: range length (n), left boundary (n), and balloon choice k (n), giving O(n^3).
- Space: The 2D dp table of size n x n stores maximum coins for each interval.
Approach 3: With Reconstruction
from typing import List, Tuple
def maxCoins_with_order(nums: List[int]) -> Tuple[int, List[int]]:
"""
Find max coins and return the optimal burst order.
Args:
nums: Array of balloon values
Returns:
Tuple of (max coins, list of indices in burst order)
Time Complexity: O(n^3)
Space Complexity: O(n^2)
"""
balloons = [1] + nums + [1]
n = len(balloons)
dp = [[0] * n for _ in range(n)]
choice = [[0] * n for _ in range(n)] # Track which balloon bursts last
for length in range(2, n):
for left in range(n - length):
right = left + length
for k in range(left + 1, right):
coins = balloons[left] * balloons[k] * balloons[right]
total = dp[left][k] + coins + dp[k][right]
if total > dp[left][right]:
dp[left][right] = total
choice[left][right] = k
# Reconstruct burst order (reverse of "last burst" order)
def get_burst_order(left: int, right: int) -> List[int]:
if left + 1 >= right:
return []
k = choice[left][right]
# k is bursted LAST in this range
# So first burst left part, then right part, then k
order = get_burst_order(left, k)
order.extend(get_burst_order(k, right))
order.append(k - 1) # Convert back to original indexing
return order
return dp[0][n - 1], get_burst_order(0, n - 1)Complexity: Time O(n^3) · Space O(n^2)
- Time: Same O(n^3) DP computation, plus O(n) for reconstructing the burst order via recursion.
- Space: Two n x n tables (dp and choice) for O(n^2) space, plus O(n) recursion stack for reconstruction.
Complexity Analysis
| Approach | Time Complexity | Space Complexity |
|---|---|---|
| Top-Down | O(n^3) | O(n^2) |
| Bottom-Up | O(n^3) | O(n^2) |
The O(n^3) comes from:
- O(n^2) subproblems (all pairs left, right)
- O(n) choices for k in each subproblem
Edge Cases
Single balloon:
pythonmaxCoins([10]) # Returns 10 (1 * 10 * 1)Two balloons:
pythonmaxCoins([3, 5]) # Returns max(1*3*5 + 1*5*1, 1*5*3 + 1*3*1) = 20All zeros:
pythonmaxCoins([0, 0, 0]) # Returns 0Array with zero:
pythonmaxCoins([3, 0, 5]) # Zero balloon gives 0 coins but affects neighborsAll same values:
pythonmaxCoins([5, 5, 5]) # Any order gives same result
DP Table Visualization
nums = [3, 1, 5], balloons = [1, 3, 1, 5, 1]
DP Table: dp[i][j] = max coins bursting balloons strictly between i and j
j=0 j=1 j=2 j=3 j=4
+----+----+----+----+----+
i=0 | 0 | 0 | 3 | 30 | 35 |
+----+----+----+----+----+
i=1 | - | 0 | 0 | 15 | 30 |
+----+----+----+----+----+
i=2 | - | - | 0 | 0 | 5 |
+----+----+----+----+----+
i=3 | - | - | - | 0 | 0 |
+----+----+----+----+----+
i=4 | - | - | - | - | 0 |
+----+----+----+----+----+
Tracing the subproblems:
dp[0][2] means burst balloons between 0 and 2, only balloon at index 1 (value 3)
k=1: coins = 1*3*1 = 3, dp[0][2] = 3
dp[2][4] means burst balloons between 2 and 4, only balloon at index 3 (value 5)
k=3: coins = 1*5*1 = 5, dp[2][4] = 5
dp[0][3] means burst balloons between 0 and 3, balloons at 1 (3) and 2 (1)
k=1: 1*3*5 + dp[0][1] + dp[1][3] = 15 + 0 + 15 = 30
k=2: 1*1*5 + dp[0][2] + dp[2][3] = 5 + 3 + 0 = 8
dp[0][3] = 30
And so on...Key Insights
Reverse Thinking: Instead of "which to burst first", think "which to burst last" in each range.
Boundary Trick: Add virtual balloons with value 1 at boundaries to simplify edge cases.
Interval DP: Classic interval DP where dp[i][j] represents the answer for range (i, j).
Independence: Once we fix the last balloon k, the left and right subproblems become independent.
Similar to Matrix Chain: Both are O(n^3) interval DP problems with similar structure.
Related Problems
Matrix Chain Multiplication (Classic)
Problem: Given matrix dimensions, find minimum scalar multiplications to compute product.
Key Insight: Identical interval DP structure. Choose split point to minimize cost.
Approach: dp[i][j] = min(dp[i][k] + dp[k+1][j] + dims[i-1]*dims[k]*dims[j]). Same O(n^3) complexity.
Complexity: O(n^3) time, O(n^2) space
Minimum Cost to Merge Stones (LeetCode 1000)
Problem: Merge k consecutive piles into one, cost = sum of merged piles. Find minimum cost to merge all into one pile.
Key Insight: Interval DP with extra constraint - can only merge k piles at a time.
Approach: dp[i][j] = min cost to merge stones[i:j+1] into minimum possible piles. Can only fully merge when (j-i) % (k-1) == 0.
Complexity: O(n^3 / k) time, O(n^2) space
Strange Printer (LeetCode 664)
Problem: Printer prints sequences of same character. Find minimum turns to print string s.
Key Insight: Interval DP. If s[i] == s[j], can print s[i] while printing s[j] for "free".
Approach: dp[i][j] = dp[i][j-1] if s[i] == s[j], else min(dp[i][k] + dp[k+1][j]) for all splits. Print overlapping characters together.
Complexity: O(n^3) time, O(n^2) space
Remove Boxes (LeetCode 546)
Problem: Remove boxes of same color together, earn k^2 points for k boxes. Maximize total points.
Key Insight: More complex than standard interval DP. Need extra state to track consecutive same-color boxes.
Approach: dp[i][j][k] = max points for boxes[i:j+1] with k extra boxes of color boxes[j] attached. Either remove immediately or find matching colors to combine.
Complexity: O(n^4) time, O(n^3) space