Lesson 200 of 250
312. Burst Balloons
NeetCode: Burst Balloons · LeetCode: 312. Burst Balloons · Difficulty: Hard
You have n balloons with numbers nums[i] (an int[]; the method is int maxCoins(int[] nums)). Bursting balloon i earns nums[i-1] * nums[i] * nums[i+1] coins, where a neighbour outside the array counts as 1. After a burst, the neighbours become adjacent. Return the maximum coins from bursting all balloons.
Example
Input: nums = [3, 1, 5, 8] Output: 167 (burst 1, 5, 3, 8: 15 + 120 + 24 + 8)
Input: nums = [1, 5] Output: 10Brute force: try every bursting order
Try each balloon as the next one to burst, collect its coins from its current neighbours, remove it and recurse on what is left. The code keeps the balloons in an ArrayList<Integer>; remove(i) with an int argument removes by index, and add(i, v) puts the balloon back after the recursive call. Each remove and add shifts elements, which costs O(n) and is dominated by the O(n!) branching. That explores every ordering of the balloons.
import java.util.ArrayList;
import java.util.List;
class Solution {
private int burst(List<Integer> nums) {
if (nums.isEmpty()) {
return 0;
}
int best = 0;
for (int i = 0; i < nums.size(); i++) {
int left = i > 0 ? nums.get(i - 1) : 1;
int right = i + 1 < nums.size() ? nums.get(i + 1) : 1;
int v = nums.remove(i); // remove(int index) returns the removed element
int gain = left * v * right + burst(nums);
nums.add(i, v);
best = Math.max(best, gain);
}
return best;
}
public int maxCoins(int[] nums) {
List<Integer> list = new ArrayList<>();
for (int x : nums) {
list.add(x);
}
return burst(list);
}
}
public class Main {
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.maxCoins(new int[] {3, 1, 5, 8}));
System.out.println(sol.maxCoins(new int[] {1, 5}));
System.out.println(sol.maxCoins(new int[] {7}));
System.out.println(sol.maxCoins(new int[] {}));
}
}167
10
7
0Time: O(n!). Space: O(n).
Better: think about the last balloon (memoised)
Reverse the question. Bursting a balloon first changes its neighbours' neighbours, so the leftover problem cannot be split. Instead ask which balloon is burst last in an interval.
Pad the array with a 1 on each end (a new int[] of length n + 2). In the memoised version, -1 in the table means not computed, since a real result is never negative. Let solve(l, r) be the best coins from bursting everything strictly between l and r. If k is the last balloon burst in that gap, its neighbours at that moment are exactly l and r, and the balloons in (l, k) and (k, r) were burst earlier and independently:
solve(l, r) = max over k in (l, r) of solve(l, k) + solve(k, r) + v[l] * v[k] * v[r]
For [3, 1, 5, 8], padded to [1, 3, 1, 5, 8, 1], solve(0, 5) tries each k as the last balloon; k = 4 (the 8) gives solve(0, 4) + solve(4, 5) + 1 * 8 * 1.
class Solution {
private int solve(int[] v, int l, int r, int[][] memo) {
if (r - l < 2) {
return 0; // nothing strictly between l and r
}
if (memo[l][r] >= 0) {
return memo[l][r]; // -1 means not computed
}
int best = 0;
for (int k = l + 1; k < r; k++) {
int coins = solve(v, l, k, memo) + solve(v, k, r, memo) + v[l] * v[k] * v[r];
best = Math.max(best, coins);
}
memo[l][r] = best;
return best;
}
public int maxCoins(int[] nums) {
int m = nums.length + 2;
int[] v = new int[m];
v[0] = 1;
v[m - 1] = 1;
System.arraycopy(nums, 0, v, 1, nums.length);
int[][] memo = new int[m][m];
for (int[] row : memo) {
java.util.Arrays.fill(row, -1);
}
return solve(v, 0, m - 1, memo);
}
}
public class Main {
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.maxCoins(new int[] {3, 1, 5, 8}));
System.out.println(sol.maxCoins(new int[] {1, 5}));
System.out.println(sol.maxCoins(new int[] {7}));
System.out.println(sol.maxCoins(new int[] {}));
}
}167
10
7
0Time: O(n^3): O(n^2) intervals, up to n choices of k each. Space: O(n^2) table plus O(n) recursion depth.
Optimal: bottom-up interval DP
Fill dp[l][r] by increasing gap length r - l = 2, 3, .... Both dp[l][k] and dp[k][r] have a shorter gap than dp[l][r], so they are already computed. The recurrence is the same as above, without recursion.
class Solution {
public int maxCoins(int[] nums) {
int m = nums.length + 2;
int[] v = new int[m];
v[0] = 1;
v[m - 1] = 1;
System.arraycopy(nums, 0, v, 1, nums.length);
// dp[l][r]: best coins from bursting everything strictly between l and r
int[][] dp = new int[m][m];
for (int gap = 2; gap < m; gap++) {
for (int l = 0; l + gap < m; l++) {
int r = l + gap;
int best = 0;
for (int k = l + 1; k < r; k++) {
best = Math.max(best, dp[l][k] + dp[k][r] + v[l] * v[k] * v[r]);
}
dp[l][r] = best;
}
}
return dp[0][m - 1];
}
}
public class Main {
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.maxCoins(new int[] {3, 1, 5, 8}));
System.out.println(sol.maxCoins(new int[] {1, 5}));
System.out.println(sol.maxCoins(new int[] {7}));
System.out.println(sol.maxCoins(new int[] {}));
}
}167
10
7
0Time: O(n^3). Space: O(n^2). The table cannot shrink to a single row because dp[l][r] reads entries from many different rows and columns.
Edge cases
- Empty input returns
0; a single balloon returns its own value (both neighbours are the padding1). - Balloons with value
0are handled by the same recurrence. - Products of three values up to
100are summed over up to300balloons: each burst is worth at most100 * 100 * 100 = 10^6, The total is anintsum, which wraps silently on overflow, so much larger values or many more balloons would needlong.
How to explain it in an interview
- Start with the brute force: try every bursting order,
O(n!). The neighbours change after each burst, which makes the problem hard to split. - Key insight: pick the last balloon burst in an interval. Its neighbours are then the interval ends, so the two sides are independent subproblems.
- Steps: pad with
1on both ends; loopkfroml+1tor-1; coins aredp[l][k] + dp[k][r] + v[l]*v[k]*v[r]; fill by increasing gap (or memoise on(l, r)). - Complexity:
O(n^3)time,O(n^2)space. - Pitfall: choosing the first balloon fails because the halves are no longer independent. The bounds are exclusive:
landrare never burst insidesolve(l, r).
