题目描述:
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
这题的分治思想并不是很直观,因为divide并不是可以一次性地将一个问题分成两个子问题。(我们应该跳出分治一定是分成两个子问题的这种思维定势),而且conquer的时候也不是常规意义的合并,而是取max。
不过直接采用递归的思想去思考,还是比较容易的。但写算法的话就可以用DP了,递归会超时。
dp(i, j) = max{dp(i, k) + dp(k, j) + nums[i]*nums[k}*nums[j]}, k = [i+1, j-1]
dp(i,j)表示数组arr[i,j]对应的该问题的最优解,其中arr[i]和arr[j]都是不能被burst的气球。
而上面的递归式右边max大括号里的表达式表示,最后一个burst的气球是第k个时,我们能够得到的最多的硬币的数目。
另外,这里填充dp的方式也不是常规的i从0到n-1,j也从0到n-1,而是按照j与i之间的间隔逐渐增大的对角线的方式进行填充。至于为什么要用这种方式填充,上面的递推式给出了答案。我们要求dp(i,j)时,一定要预先知道i和j之间的任意区间的dp值,而我们如果采用i从0到n-1,j也从0到n-1或者由大到小的迭代方式都没法满足这个条件,即如果i从小到大,j也从小到大,那么求dp(i,j)时,需要用到dp(i,k)和dp(k,j),i<k<h,其中dp(i,k)会在它之前得到,但dp(k,j)就还没计算到,因为k>i。
而经过观察我们发现,dp(i,k)和dp(k,j)的下标区间长度一定比dp(i,j)要小,所以我们可以采用区间长度由小到大的方式进行填充dp数组。
问题想明白了的话,其实代码还是相对比较容易写的。
class Solution {
public:
int maxCoins(vector<int>& nums) {
if(0 == nums.size()) return 0;
nums.insert(nums.begin(), 1);
nums.push_back(1);
int n = nums.size();
vector<int> col(n, 0);
vector<vector<int>> dp(n, col);
for(int len = 2; len < n; ++len)
{
for(int i = 0; i < n-len; ++i)
{
int ret = 0;
for(int k = i+1; k < i+len; ++k)
{
int tmp = dp[i][k] + dp[k][i+len] + nums[i]*nums[k]*nums[i+len];
if(tmp > ret) ret = tmp;
}
dp[i][i+len] = ret;
}
}
return dp[0][n-1];
}
};