Burst balloons for the most coins
Balloons in a row carry the numbers nums. Bursting balloon i earns
left · nums[i] · right, where left and right are the numbers on its current neighbours (a missing neighbour counts as
1); the burst balloon then disappears and its neighbours become adjacent. Burst them all and return the most coins
possible.
Examples
| Input | Output | Why |
|---|---|---|
nums = [3,1,5,8] | 167 | Burst 1, 5, 3, 8: 15 + 120 + 24 + 8. |
nums = [1,5] | 10 | Burst 1 (5 coins), then 5 (5 coins). |
nums = [7] | 7 | One balloon, neighbours count as 1. |
Constraints
- 1 ≤ nums.length ≤ 300
- 0 ≤ nums[i] ≤ 100
Hints
Hint 1
Pad the row with a 1 at each end. Think about the range strictly between two padded positions i and j.
Hint 2
Choose the balloon k that is burst LAST in that range: at that moment its neighbours are exactly i and j.
Hint 3
coins(i, j) = max over k of coins(i, k) + coins(k, j) + v[i] · v[k] · v[j].
Where people lose marks
- Choosing the first balloon burst, which leaves the two sides dependent.
- Forgetting the padding ones.
- Filling the table by rows instead of by range length.
Dry run on [3,1,5,8]
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 4padded103112538415
Ranges with 1 balloon: (0,2) last 3: 3; (1,3) last 1: 15; (2,4) last 5: 40; (3,5) last 8: 40.
-
Step 2 / 4padded103112538415
Ranges with 2 balloons: (0,3) last 3: 30; (1,4) last 5: 135; (2,5) last 8: 48.
-
Step 3 / 4padded103112538415
Ranges with 3 balloons: (0,4) last 3: 159; (1,5) last 8: 159.
-
Step 4 / 4padded103112538415
Ranges with 4 balloons: (0,5) last 8: 167. The answer is 167.
How to think about it
Choosing the first balloon to burst does not split the problem: its removal joins its neighbours, so the two sides
interact. Choosing the last one does. If k is the last balloon burst strictly between positions i and j (of the row
padded with 1s), every other balloon in (i, k) and (k, j) is gone before it, so those two ranges are independent, and when
k finally bursts its neighbours are i and j. Hence coins(i, j) = max over k of coins(i, k) + coins(k, j) + v[i] · v[k] ·
v[j], filled by increasing gap j − i. The answer is the range from the first padding to the last.
Complexity. Time O(n³) · Space O(n²)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def max_coins(nums):
v = [1] + nums + [1] # padding: missing neighbours count as 1
n = len(v)
coins = [[0] * n for _ in range(n)] # coins[i][j]: best for the balloons strictly between i and j
for gap in range(2, n):
for i in range(n - gap):
j = i + gap
coins[i][j] = max(coins[i][k] + coins[k][j] + v[i] * v[k] * v[j]
for k in range(i + 1, j)) # k is burst last
return coins[0][n - 1]#include <algorithm>
#include <vector>
int solve(const std::vector<int>& nums) {
std::vector<int> v{1};
v.insert(v.end(), nums.begin(), nums.end());
v.push_back(1);
const int n = v.size();
std::vector<std::vector<int>> coins(n, std::vector<int>(n, 0));
for (int gap = 2; gap < n; ++gap)
for (int i = 0; i + gap < n; ++i) {
int j = i + gap;
for (int k = i + 1; k < j; ++k) // k is burst last
coins[i][j] = std::max(coins[i][j], coins[i][k] + coins[k][j] + v[i] * v[k] * v[j]);
}
return coins[0][n - 1];
}class Solution {
public int maxCoins(int[] nums) {
int n = nums.length + 2;
int[] v = new int[n];
v[0] = 1; v[n - 1] = 1;
for (int i = 0; i < nums.length; i++) v[i + 1] = nums[i];
int[][] coins = new int[n][n];
for (int gap = 2; gap < n; gap++)
for (int i = 0; i + gap < n; i++) {
int j = i + gap;
for (int k = i + 1; k < j; k++) // k is burst last
coins[i][j] = Math.max(coins[i][j], coins[i][k] + coins[k][j] + v[i] * v[k] * v[j]);
}
return coins[0][n - 1];
}
}function maxCoins(nums) {
const v = [1, ...nums, 1], n = v.length;
const coins = Array.from({ length: n }, () => new Array(n).fill(0));
for (let gap = 2; gap < n; gap++)
for (let i = 0; i + gap < n; i++) {
const j = i + gap;
for (let k = i + 1; k < j; k++) // k is burst last
coins[i][j] = Math.max(coins[i][j], coins[i][k] + coins[k][j] + v[i] * v[k] * v[j]);
}
return coins[0][n - 1];
}fn solve(nums: &[i64]) -> i64 {
let mut v = vec![1i64];
v.extend_from_slice(nums);
v.push(1);
let n = v.len();
let mut coins = vec![vec![0i64; n]; n];
for gap in 2..n {
for i in 0..n - gap {
let j = i + gap;
for k in i + 1..j { // k is burst last
coins[i][j] = coins[i][j].max(coins[i][k] + coins[k][j] + v[i] * v[k] * v[j]);
}
}
}
coins[0][n - 1]
}import Data.Array
solve :: [Int] -> Int
solve nums = coins ! (0, n - 1)
where
vs = [1] ++ nums ++ [1]
n = length vs
v = listArray (0, n - 1) vs
-- coins ! (i, j): the most for the balloons strictly between i and j; k is the last one burst
coins = array ((0, 0), (n - 1, n - 1)) [((i, j), go i j) | i <- [0 .. n - 1], j <- [0 .. n - 1]] :: Array (Int, Int) Int
go i j
| j - i < 2 = 0
| otherwise = maximum [coins ! (i, k) + coins ! (k, j) + v ! i * v ! k * v ! j | k <- [i + 1 .. j - 1]]
Variants to try
- Return the order of bursts.
- Neighbours are the two nearest on each side (wider windows).
- Minimise instead of maximise.
Next problems
The cheapest way to cut a stick
Each cut costs the length of the piece being cut. Choose the order of cuts to minimise the total: an interval DP over the sorted cut positions.
Open the problem → Medium20 minCan the first player win?
Players alternately take a number from either end of a row. With both playing perfectly, does the first player finish with at least as much?
Open the problem → Easy10 minThe cheapest way up a staircase
Each step has a cost; climb one or two steps at a time; start on step 0 or 1. Return the cheapest way past the top.
Open the problem →Write a solution and run it against the real test table.
Keep going
The cheapest way to cut a stick
Each cut costs the length of the piece being cut. Choose the order of cuts to minimise the total: an interval DP over the sorted cut positions.
Open the problem → Medium20 minCan the first player win?
Players alternately take a number from either end of a row. With both playing perfectly, does the first player finish with at least as much?
Open the problem → Easy10 minThe cheapest way up a staircase
Each step has a cost; climb one or two steps at a time; start on step 0 or 1. Return the cheapest way past the top.
Open the problem → Dynamic programmingThe pattern behind it
Write the recursion, notice the repeats, fill a table in an order that makes each entry final.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.