312. Burst Balloons
Givenn
balloons, indexed from0
ton-1
. Each balloon is painted with a number on it represented by arraynums
. You are asked to burst all the balloons. If the you burst ballooni
you will getnums[left] * nums[i] * nums[right]
coins. Hereleft
andright
are adjacent indices ofi
. After the burst, theleft
andright
then becomes adjacent.
Find the maximum coins you can collect by bursting the balloons wisely.
Note:
- You may imagine
nums[-1] = nums[n] = 1
. They are not real therefore you can not burst them. - 0 ≤
n
≤ 500, 0 ≤nums[i]
≤ 100
Example:
Input:[3,1,5,8]
Output:167
Explanation:
nums = [3,1,5,8] -->[3,5,8] -->[3,8] -->[8] -- >[]
coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167
Thoughts:
- dp[left][right]: ... from subarray[left ... right]
- recursive function: dp[left][right] = max(..., dp[left][i], nums[left] * nums[i] * nums[right] + dp[i][right])
- propagating direction: from small sliding window to larger sliding window
- return dp[0][n-1] (from left = 0 to right n-1)
Code T: O(n^3) & S: O(n^2)
class Solution {
public int maxCoins(int[] nums) {
// first busing 0s
int [] nums_pad = new int [nums.length + 2];
int n = 1;
for (int x : nums) if( x > 0) nums_pad[n++] = x;
nums_pad[0] = nums_pad[n++] = 1;
int [][] dp = new int [n][n];
for(int s = 2; s < n; s++){ // s is the length of the sliding window
for(int left = 0 ; left + s < n; left++){
int right = left + s;
for(int i = left + 1; i < right; i++){
dp[left][right] = Math.max(dp[left][right],
dp[left][i] + nums_pad[left] * nums_pad[i] * nums_pad[right] + dp[i][right]
);
}
}
}
return dp[0][n-1];
}
}
class Solution {
public:
int maxCoins(vector<int>& nums) {
// first bursting 0s
int nums_pad [nums.size() + 2];
int n = 1;
for (int x : nums) if( x > 0) nums_pad[n++] = x;
nums_pad[0] = nums_pad[n++] = 1;
int dp[n][n] = {};
for(int s = 2; s < n; s++){
for(int left = 0 ; left + s < n; left++){
int right = left + s;
for(int i = left + 1; i < right; i++){
dp[left][right] = max(dp[left][right],
dp[left][i] + nums_pad[left] * nums_pad[i] * nums_pad[right] + dp[i][right]
);
}
}
}
return dp[0][n-1];
}
};
class Solution(object):
def maxCoins(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
nums_pad = [1] + [x for x in nums if x > 0] + [1]
n = len(nums_pad)
dp = [[0]* n for _ in range(n)] # a fancy way of building the nxn matrix filled with 0
for s in range(2,n):
for left in range(0, n - s):
right = left + s
for i in range(left + 1, right):
dp[left][right] = max(dp[left][right], dp[left][i] + nums_pad[left] *nums_pad[i]* nums_pad[right] + dp[i][right])
return dp[0][n-1]
A Divide & Conquer solution
class Solution {
public int maxCoins(int[] nums) {
// first busing 0s
int [] nums_pad = new int [nums.length + 2];
int n = 1;
for (int x : nums) if(x > 0) nums_pad[n++] = x;
nums_pad[0] = nums_pad[n++] = 1;
int [][] mem = new int [n][n]; // memoization step
return burstBallon(mem, nums_pad, 0 , n -1);
}
public int burstBallon(int[][] mem, int [] nums, int left, int right){
// top down + bottom up fashion
// base case
if (left == right -1) return 0;
if (mem[left][right] > 0) return mem[left][right];
int ans = 0;
// caculation step
for(int i = left + 1; i < right; i++){
ans = Math.max(ans, nums[left] * nums[i] * nums[right] + burstBallon(mem,nums, left, i) + burstBallon(mem,nums, i, right));
}
mem[left][right] = ans; //memoization step
return ans;
}
}
Note: In this case the reason why recursive solution with memoization is faster is because for some DP problems, we might not have to examine "all" the sub-problems, which most of the bottom-up table building iterative solutions do. In those cases, recursive solution examine less sub-problems, and that compensate for the overhead from recursion.
However, iterative solution is definitely faster than recursive solution for this problem (in Python). The reason is that they both "fill up" the same sub-solutions in the dp array, and recursion always has more overhead than iteration.
from dietpepsi's post and explanation by StrongerXi