312. Burst Balloons
Problem Description
You are presented with a hypothetical situation involving n
balloons, each painted with a number. These balloons are indexed from 0
to n - 1
and the numbers painted on them are given in an array called nums
. Your task is to "burst" all of these balloons in such a way that allows you to collect the maximum number of coins possible.
The main rule to remember is that when you burst a balloon at index i
, you collect a number of coins equal to the product of the numbers on three balloons: the one at i
, the one immediately before it (i - 1
), and the one immediately after it (i + 1
). If there is no balloon at i - 1
or i + 1
(which would be the case if i
is at the start or end of the array), then you pretend there is a balloon with the number 1
painted on it.
The challenge is to find the sequence of bursting balloons that yields the highest total number of coins.
Intuition
To arrive at a solution for this problem, consider that the last balloon you burst would allow you to collect nums[0] * nums[i] * nums[n-1]
coins, where i
is the index of this last balloon. Since nums[0]
and nums[n-1]
would be out of bounds after bursting all other balloons, we treat them as 1
.
This suggests a dynamic programming approach where we consider smaller subproblems and build up to the full solution. The intuition behind the provided solution is to transform the problem into considering the maximum coins that can be collected by bursting the balloons within a subinterval of the original array.
By iteratively expanding these intervals and calculating the maximum number of coins that can be collected when bursting the last balloon in these subintervals, we can use previously solved smaller intervals to help calculate larger intervals. This concept is similar to matrix chain multiplication, where optimal solutions to smaller subproblems can be used to construct the final solution.
The idea is to iteratively consider all possible subintervals and all possible last balloons to burst within each subinterval. By doing so, we use the dynamic programming (DP) array dp
to store the maximum coins obtained for each subinterval, so that we do not have to re-calculate the coins for those intervals again. The process starts from the smallest intervals and works its way up to the interval covering the entire range.
In the implementation, the dp
array is first initialized to store zero values. Then, for each subinterval length l
, starting at 2
and going up to n
, we calculate the maximum coins for starting subintervals at index i
and ending at j
, using all possible last balloons k
to find the best option. The final answer is then taken from the dp
entry that covers the entire range, excluding the artificial balloons at both ends (that only serve the purpose of simplifying boundary conditions).
Learn more about Dynamic Programming patterns.
Solution Approach
The solution approach involves dynamic programming, which is a method for solving complex problems by breaking them down into simpler subproblems. The solution uses a 2D array, often referred to as a "DP table", to store the maximum number of coins that can be collected by bursting balloons within a specific subinterval of the array.
Let's delve into how the solution is implemented:
-
We first add a
1
at the beginning and the end of thenums
array. This step simplifies our calculations for cases when we burst the first or the last balloon in the original array. -
We define
n
as the length of this new array, now including the two additional balloons with a1
painted on them. -
We initialize a 2D array called
dp
with dimensionsn
byn
, setting all values to0
.dp[i][j]
will store the maximum coins that can be collected by bursting all balloons between indexi
andj
. -
We iterate through all possible lengths of subintervals
l
, starting from2
. We choose2
because a subinterval must contain at least one balloon to burst (situated between the two1
s we added). -
For each subinterval length (
l
), we find the start (i
) and end (j
) of the subinterval. We restrict our subinterval to be within the bounds of the array. -
For each subinterval from
i
toj
, we consider each balloonk
as the last balloon to burst in this subinterval. We calculate the coins collected from bursting balloonk
last, which isnums[i] * nums[k] * nums[j]
, and add to it the coins collected from the subintervals[i, k]
and[k, j]
which we have already calculated and stored in ourdp
table previously. -
We update
dp[i][j]
with the maximum number of coins found for bursting all balloons betweeni
andj
.
The nested loops in the code iterate over subintervals and the potential last balloons that could be burst in that interval. The innermost loop picks the best choice for the last balloon to maximize the coins collected.
Finally, the result is the value in dp[0][n-1]
, where n-1
is the index after the last artificial 1
.
This solution effectively builds the solution from smaller intervals to larger ones and uses previously computed results for efficiency, avoiding redundant calculations.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider an example with the following balloon numbers: nums = [3, 1, 5, 8]
. Following the given solution approach:
-
We first add
1
at the beginning and the end to get[1, 3, 1, 5, 8, 1]
to account for the boundaries. -
The length
n
of the new array is6
. -
We initialize a
6x6
DP table with zeros. This table will help us remember the best solutions for subproblems. -
We consider subinterval lengths
l
starting from2
, because a subinterval should have at least one balloon. -
For
l = 2
, we have subintervals[1,3]
,[2,4]
,[3,5]
, and[4,6]
:- For subinterval
[1,3]
, the only balloon to burst is3
(as1
and1
are our added boundaries), and it gives1 * 3 * 1 = 3
coins. - Similarly, for
[2,4]
, bursting1
would give3 * 1 * 5 = 15
coins, and so on for the other subintervals.
Our DP table is updated for these subintervals.
- For subinterval
-
With
l = 3
, we have subintervals[1,2,4]
,[2,3,5]
, and[3,4,6]
:- Take
[1,2,4]
, if we decide to burst balloon2
last, we get1 * 1 * 5 + dp[1][2] + dp[2][4] = 5 + 3 + 15 = 23
coins. For[2,3,5]
, the last burst3
gives3 * 5 * 1 + dp[2][3] + dp[3][5]
coins, and so on.
We update our DP table accordingly.
- Take
-
This process is continued, expanding the size of subintervals until
l = n - 1
, wheren
is the length of our new array, and our subinterval is the entire array.
For each subinterval at each l
, we consider all possible balloons that could be burst last, calculate the coins we can collect, and choose the maximum among them to update our DP table.
The result would then be in dp[0][n-1]
which in this case will store the maximum number of coins we can collect from [1, 3, 1, 5, 8, 1]
.
Walkthrough example with detailed subinterval analysis:
-
l=2
:- Subinterval
[1,3]
(burst 3):dp[1][3] = 1 * 3 * 1 + dp[1][2] + dp[3][3] = 3
- Subinterval
[2,4]
(burst 1):dp[2][4] = 3 * 1 * 5 + dp[2][2] + dp[4][4] = 15
- Subinterval
[3,5]
(burst 5):dp[3][5] = 1 * 5 * 8 + dp[3][4] + dp[5][5] = 40
- Subinterval
[4,6]
(burst 8):dp[4][6] = 5 * 8 * 1 + dp[4][5] + dp[6][6] = 40
- Subinterval
-
l=3
:- Subinterval
[1,2,4]
(choose balloon 2 to burst last for max coins):dp[1][4] = max(calculate for bursting 2 last, calculate for bursting 3 last) = max(1*1*5+dp[1][2]+dp[3][4], 1*3*5+dp[1][3]+dp[4][4]) = max(23, 15+0+0) = 23
- Subinterval
[2,3,5]
(choose balloon 3 to burst last for max coins):dp[2][5] = max(calculate for bursting 3 last, calculate for bursting 4 last) = max(3*5*1+dp[2][3]+dp[4][5], 3*1*8+dp[2][4]+dp[5][5]) = max(40+15, 15+40+0) = 55
- Subinterval
[3,4,6]
(choose balloon 4 to burst last for max coins):dp[3][6] = max(calculate for bursting 4 last, calculate for bursting 5 last) = max(1*1*1+dp[3][4]+dp[5][6], 1*5*1+dp[3][5]+dp[6][6]) = max(1+0+0, 40+0+0) = 40
- Subinterval
And continue this way until the entire array is covered, resulting in dp[1][5]
for the maximum coins.
By the end of the dynamic programming process, we find that the maximum number of coins that can be earned by bursting all the balloons is dp[0][5]
.
Solution Implementation
1class Solution:
2 def maxCoins(self, nums):
3 """
4 Calculate the maximum coins that can be obtained by bursting the balloons wisely.
5
6 :param nums: A list of integer representing the number of balloons
7 :type nums: List[int]
8 :return: The maximum coins obtained
9 :rtype: int
10 """
11 # Add a balloon with value 1 to each end of the list to simplify calculations
12 nums = [1] + nums + [1]
13 # Calculate the length of the new nums list
14 n = len(nums)
15 # Initialize the dp (Dynamic Programming) table with zeros
16 dp = [[0] * n for _ in range(n)]
17
18 # Fill the dp table
19 # l represents the length of the range we're considering
20 for l in range(2, n):
21 # i represents the start of the range
22 for i in range(n - l):
23 # j represents the end of the range
24 j = i + l
25 # Test each possible position 'k' for the balloon to burst last in the range (i, j)
26 for k in range(i + 1, j):
27 # Calculate the maximum coins by adding the coins obtained from bursting the current balloon
28 # (nums[i]*nums[k]*nums[j]) plus the maximum coins from the two subranges (i, k) and (k, j)
29 dp[i][j] = max(
30 dp[i][j], # Current max coins for range (i, j)
31 dp[i][k] + dp[k][j] + nums[i] * nums[k] * nums[j] # Max coins if balloon k is burst last
32 )
33 # The maximum coins obtained will be in the top right corner of the dp table
34 return dp[0][-1]
35
1class Solution {
2
3 // Method to calculate the maximum coins after bursting all the balloons.
4 public int maxCoins(int[] nums) {
5 // Initialize an extended array that includes 1s at both ends.
6 int[] extendedNums = new int[nums.length + 2];
7 extendedNums[0] = 1;
8 extendedNums[extendedNums.length - 1] = 1;
9
10 // Copy the original nums array into the extendedNums array, starting at index 1.
11 System.arraycopy(nums, 0, extendedNums, 1, nums.length);
12
13 // Length of the extended array.
14 int n = extendedNums.length;
15
16 // Create a DP table to store the results of subproblems.
17 int[][] dp = new int[n][n];
18
19 // Iterate over all possible lengths (starting from 2 because we need at least
20 // one balloon between i and j to calculate coins).
21 for (int length = 2; length < n; ++length) {
22 // Iterate over the start index of the subarray.
23 for (int i = 0; i + length < n; ++i) {
24 // End index of the subarray.
25 int j = i + length;
26
27 // Iterate over all possible last balloons to burst between i and j.
28 for (int k = i + 1; k < j; ++k) {
29 // Update the DP table with the maximum coins from either the previously
30 // computed number or the new number of coins obtained by bursting the
31 // current balloon last, along with previously computed subresults.
32 dp[i][j] = Math.max(dp[i][j],
33 dp[i][k] + dp[k][j] + extendedNums[i] * extendedNums[k] * extendedNums[j]);
34 }
35 }
36 }
37
38 // Final answer is the maximum coins we can get for the entire array,
39 // which is stored in dp[0][n - 1].
40 return dp[0][n - 1];
41 }
42}
43
1class Solution {
2public:
3 int maxCoins(vector<int>& nums) {
4 // Add 1 before the first and after the last element to handle edge cases
5 nums.insert(nums.begin(), 1);
6 nums.push_back(1);
7
8 // Calculate the size of the modified vector
9 int n = nums.size();
10
11 // Initialize a 2D DP array with 0s
12 vector<vector<int>> dp(n, vector<int>(n, 0));
13
14 // Iterate over the balloons in windows of increasing size
15 for (int windowLength = 2; windowLength < n; ++windowLength) {
16 // i is the start index of the current window
17 for (int i = 0; i + windowLength < n; ++i) {
18 // j is the end index of the current window
19 int j = i + windowLength;
20 // k is the index of the last balloon to be burst in the current window
21 for (int k = i + 1; k < j; ++k) {
22 // Update the DP table with the maximum coins obtained by bursting
23 // the last balloon at index k in the current window (subarray [i, j])
24 // The equation considers the coins obtained from bursting balloons in
25 // the subarrays [i, k] and [k, j], along with the coins from bursting
26 // the k-th balloon with its adjacent balloons at indices i and j
27 dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + nums[i] * nums[k] * nums[j]);
28 }
29 }
30 }
31
32 // The optimal solution for the entire array is stored in dp[0][n - 1]
33 return dp[0][n - 1];
34 }
35};
36
1function maxCoins(nums: number[]): number {
2 // Obtain the length of the input array
3 let numberOfBalloons = nums.length;
4
5 // Initialize a 2D array `dp` (Dynamic Programming table) for memoization
6 // with dimensions (numberOfBalloons + 1) x (numberOfBalloons + 2),
7 // defaulting all cells to 0.
8 let dp = Array.from({ length: numberOfBalloons + 1 }, () => new Array(numberOfBalloons + 2).fill(0));
9
10 // Pad the array of nums with 1 on both ends to simplify edge cases.
11 nums.unshift(1);
12 nums.push(1);
13
14 // Reverse iterate over the array to calculate maximum coins using bottom-up approach,
15 // starting from the second to last element and going backwards.
16 for (let left = numberOfBalloons - 1; left >= 0; --left) {
17 for (let right = left + 2; right < numberOfBalloons + 2; ++right) {
18 // Check all possible balloons that can be burst between left and right
19 // excluding the boundaries, and compute the maximum coins that can be obtained.
20 for (let k = left + 1; k < right; ++k) {
21 // Calculate coins obtained by bursting k-th balloon between balloons at 'left' and 'right',
22 // and add the coins obtained from previously burst balloons on both sides.
23 let coins = nums[left] * nums[k] * nums[right] + dp[left][k] + dp[k][right];
24 dp[left][right] = Math.max(coins, dp[left][right]); // Store the maximum in the DP table.
25 }
26 }
27 }
28 // Return the maximum coins obtained by bursting all balloons except the first and last added ones.
29 return dp[0][numberOfBalloons + 1];
30}
31
Time and Space Complexity
Time Complexity
The given code implements the dynamic programming approach to solve the burst balloons problem. The time complexity of the code is dependent on the nested loops used in the algorithm.
-
There is an outer loop that iterates through all possible lengths of the subarrays (denoted by
l
), which goes from 2 ton
. So, it iteratesn - 2
times. -
For every possible length, there is a nested loop iterating over the start indices of the subarrays (denoted by
i
), and this runs forn - l
iterations for eachl
. -
Inside the second loop, there is an innermost loop that iterates through all possible k positions where the balloon can be burst, between the start and end of the subarrays (from
i + 1
toj - 1
wherej = i + l
).
The combination of these nested loops looks something like a cubic number of operations, giving us a time complexity of O(n^3)
, where n
is the number of balloons (including the two added ones).
Space Complexity
The space complexity is mainly dictated by the 2D dynamic programming array dp
, which has a size of n * n
.
As a result, the space complexity of the code is O(n^2)
, where n
is the total number of elements in the augmented array nums
.
Learn more about how to find time and space complexity quickly using problem constraints.
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!