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:
- 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
思路
解法有backtrack和dp两种,首先还是来分析题目。
这题的要素有:n 个气球,从0到n-1编号,每个气球都标记了一个硬币数字。戳爆其中一个会得到指定的coings,与相邻的两个数字的乘积是所获得的硬币数。那么如何确定戳爆顺序使得获得的硬币数目最大?难点在于不同的戳爆顺序会相互影响。
从最后戳爆的气球位置入手,如果最后戳爆的气球是位置i上的气球,那么可以肯定的是在它之前的 0~i-1 位置上的气球肯定被戳爆了,i+1到n-1位置上的气球也被戳爆了。递归遍历所有的i即可。
public int maxCoins(int[] iNums) {
int[] nums = new int[iNums.length + 2]; // 构造新数组,方便计算
int n = 1;
for (int x : iNums) if (x > 0) nums[n++] = x;
nums[0] = nums[n++] = 1; // 这里一直用n++是因为要忽视iNums的0,这样n最后不一定等于iNum的长度
int[][] memo = new int[n][n]
return burst(memo, nums, 0, n - 1); // 从1到n-1位置上搜索,即left+1~right,之所以n-1是因为上面的n++使得n到了末尾1的后一位
}
public int burst(int[][] memo, int[] nums, int left, int right) {
if (left + 1 == right) return 0; // 如果left+1==right,因为是从left+1到right-1处遍历的。遍历结束,直接返回
if (memo[left][right] > 0) return memo[left][right]; // 如果已计算过,则不需再次计算
int ans = 0;
for (int i = left + 1; i < right; ++i) // 初始从left+1到right-1,对应起来正好是iNums中的0~n-1
ans = Math.max(ans, nums[left] * nums[i] * nums[right]
+ burst(memo, nums, left, i) + burst(memo, nums, i, right));
memo[left][right] = ans;
return ans;
}
DP解法如下:
public int maxCoins(int[] iNums) { int[] nums = new int[iNums.length + 2]; int n = 1; for (int x : iNums) if (x > 0) nums[n++] = x; nums[0] = nums[n++] = 1; int[][] dp = new int[n][n]; // k表示计算步长,从right=left+2开始。因为i从left+1到right-1遍历,所以一开始只计算left和right之间只隔了一位的情况 for (int k = 2; k < n; ++k) for (int left = 0; left < n - k; ++left) { int right = left + k; for (int i = left + 1; i < right; ++i) // 在子问题dp[left][right]下,遍历每个可能的最后引爆的气球,以求出子问题的最优解 dp[left][right] = Math.max(dp[left][right], nums[left] * nums[i] * nums[right] + dp[left][i] + dp[i][right]); // 左边界是left,右边界是right } return dp[0][n - 1]; }