Given n
balloons, indexed from 0
to n-1
. Each balloon is painted with a number on it represented by array nums
. You are asked to burst all the balloons. If the you burst balloon i
you will get nums[left] * nums[i] * nums[right]
coins. Here left
and right
are adjacent indices of i
. After the burst, the left
and right
then becomes adjacent.
Find the maximum coins you can collect by bursting the balloons wisely.
Note:
(1) You may imagine nums[-1] = nums[n] = 1
. They are not real therefore you can not burst them.
(2) 0 ≤ n
≤ 500, 0 ≤ nums[i]
≤ 100
Example:
Given [3, 1, 5, 8]
Return 167
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
思路:直接照搬Discuss里面大神的思路:https://discuss.leetcode.com/topic/30746/share-some-analysis-and-explanations/77
Be Naive First
When I first get this problem, it is far from dynamic programming to me. I started with the most naive idea the backtracking.
We have n balloons to burst, which mean we have n steps in the game. In the i th step we have n-i balloons to burst, i = 0~n-1. Therefore we are looking at an algorithm of O(n!). Well, it is slow, probably works for n < 12 only.
Of course this is not the point to implement it. We need to identify the redundant works we did in it and try to optimize.
Well, we can find that for any balloons left the maxCoins does not depends on the balloons already bursted. This indicate that we can use memorization (top down) or dynamic programming (bottom up) for all the cases from small numbers of balloon until n balloons. How many cases are there? For k balloons there are C(n, k) cases and for each case it need to scan the k balloons to compare. The sum is quite big still. It is better than O(n!) but worse than O(2^n).
Better idea
We then think can we apply the divide and conquer technique? After all there seems to be many self similar sub problems from the previous analysis.
Well, the nature way to divide the problem is burst one balloon and separate the balloons into 2 sub sections one on the left and one one the right. However, in this problem the left and right become adjacent and have effects on the maxCoins in the future.
Then another interesting idea come up. Which is quite often seen in dp problem analysis. That is reverse thinking. Like I said the coins you get for a balloon does not depend on the balloons already burst. Therefore
instead of divide the problem by the first balloon to burst, we divide the problem by the last balloon to burst.
Why is that? Because only the first and last balloons we are sure of their adjacent balloons before hand!
For the first we have nums[i-1]*nums[i]*nums[i+1]
for the last we have nums[-1]*nums[i]*nums[n]
.
OK. Think about n balloons if i is the last one to burst, what now?
We can see that the balloons is again separated into 2 sections. But this time since the balloon i is the last balloon of all to burst, the left and right section now has well defined boundary and do not affect each other! Therefore we can do either recursive method with memoization or dp.
Final
Here comes the final solutions. Note that we put 2 balloons with 1 as boundaries and also burst all the zero balloons in the first round since they won't give any coins.
The algorithm runs in O(n^3) which can be easily seen from the 3 loops in dp solution.
DP[i][j]表示爆掉里面的,不包括i,j,全部爆掉就身下边界left right lastBurst
/*
* 用DP来减少重复运算
*/
public class Solution {
public int maxCoins(int[] oriNums) {
int n = oriNums.length;
int[] nums = new int[n+2];
nums[0] = 1;
int pos = 1;
for(int num : oriNums) nums[pos++] = num;
nums[pos] = 1;
int[][] dp = new int[n+2][n+2];
/*
* 3层循环,left,right,last burst各一层循环
* 但是直接用上面3个参数做DP是不行的
* 应该用right-left distance,left,last burst,只有这样在求dp[left][right]才能确保
* 比它间距更小的dp[left][lastBurst],dp[lastBurst][right]是真正的最大值(是最终的结果,后面也不会在变了)
* 而如果用left,right,last burst各一层循环,仔细想想就知道并不满足这一条件
*/
for(int dis=2; dis<=n+1; dis++) {
for(int left=0; left<=n+1-dis; left++) {
int right = left + dis;
for(int lastBurst=left+1; lastBurst
二刷:
其实直接写DP谁TM想的出来!!!
拿到这个题目,naive的做法是DFS暴力递归,先burst哪个,再burst哪个。这样下去,
如果直接用循环做有没有可能?先burst某个之后,再分成若干个子问题,divide and conquer?不行,并不能分成2个独立求最大值 的子问题,因为burst某个之后剩余的可能是左右两边耦合才打到最大的coins呢?
换种思维,如果假定打算burst的这个是最后一个要burst的呢?这个位置无形就相当于一个wall把左右两边隔离开来了,这样就可以放心的用Divide & Conquer了!
再用点memorization就可以AC了!!有了递归和Memorization当然也可以写出DP了!
/*
* 最直观的考虑就是第一个burst哪个,然后接着考虑下一个burst的位置
* 但是这样感觉没有止境,就像之前的241. Different Ways to Add Parentheses一样
* 那考虑某一个balloons为最后一个burst的,分治左右两边求最大
* 因为要burst子数组的时候后要用到Boundary,所以先对nums扩容
*/
public class Solution {
int[][] memory;
public int maxCoins(int[] nums) {
int[] A = new int[nums.length+2];
A[0] = 1; A[A.length-1] = 1;
for(int i=1; i j) return 0;
if(memory[i][j] != 0) return memory[i][j];
int max = 0;
for(int k=i; k<=j; k++) {
int left = maxCoins(nums, i, k-1);
int right = maxCoins(nums, k+1, j);
max = Math.max(max, left+right+nums[i-1]*nums[k]*nums[j+1]);
}
memory[i][j] = max;
return max;
}
}