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
一道DP题目,我对动态规划的理解还仅停留在0-1背包问题,理所当然的这题目我连别人的解析都看不懂,花了很长时间终于明白了,这里记录下来,看不懂https://leetcode.com/problems/burst-balloons/discuss/76230/C++-dp-detailed-explanation的童鞋可以看一看我这个更啰嗦的版本:)
1.这道题目里难点之一是选什么做状态值?dp[left][right]
代表的是子序列nums[left]...nums[right]
能获得的最大硬币数
2.如果能想到第1点,根据题意,就能写出dp矩阵的初始值,具体请看下面的演算过程(以[3,1,5,8]为例),很明显dp应该是一个(num.size()+2) * (num.size()+2)的矩阵,初始值如下:
然后我们先来更新对角线上的值,也就是dp[index][index],0<=index<=n-1。以dp[2][2]为例,其表示nums[2]...nums[2]序列能获得的最大硬币数。
[3,1,{5},8],{}中表示这次选中的子序列,很明显,dp[2][2] = 1*5*8 = 40,因为我们只能把编号为5的气球打破。于是我们得到了下述dp矩阵值:
3.接下来又是一个难点:如何计算剩余的dp值,如何确定状态转移方程,因为我们的目标是得到右上角的dp[0][3]值。
先给出状态转移方程:
dp[left][right] = max(dp[left][right], nums[left-1]*nums[k]*nums[right+1] + dp[left][k-1] + dp[k+1][right]);
对于这个状态转移方程,我们还是举例子说明如何计算dp[i][j],i<j.以dp[0][1]为例,即[{3,1},5,8],现有两种可能:
1.3是最后一个被打破的气球,此时3旁边的数字是1和5,也就是nums[i-1]与nums[j+1],这种选择方案的分数是3*1*5+1*3*5 = 30,其中3*1*5
是打破编号为1的气球所得的分数,注意,这也是dp[1][1]的值;
2.1是最后一个被打破的气球,此时1旁边的数字是1和5,这种选择方案的分数是 1*3*1 + 1*1*5 = 8,其中1*3*1是dp[0][0]的值
总结一下,我们发现dp[1][1] = dp[k+1][right],dp[0][0] = dp[left][k-1],这样就可以对状态转移方程有一个直观的理解了:
计算dp[i][j]的思路是依次假定i到j中一个气球是最后打破的,比如说k(k在i到j之间)是最后打破的,那么在这个假定成立的前提下,最大收益是
nums[i-1]*nums[k]*nums[j+1] + dp[i][k-1] + dp[k+1][j]
按照对角线的方向,依次我们可以计算出:
4.如果从nums数组的角度上来看,实际上dp数组的计算是一个从左到右滑动的,并且不断增大的滑动窗口,如下所示:
[{3},1,5,8]...[3,1,5,{8}],[{3,1},5,8]...[3,1,{5,8}],[{3,1,5},8]......
最后贴上程序,DP的题目思路有了就非常简单了。
class Solution {
public:
int maxCoins(vector<int>& nums) {
int n = nums.size();
nums.insert(nums.begin(), 1);
nums.push_back(1);
vector<vector<int>> dp(nums.size(), vector<int>(nums.size(), 0));
for (int len = 1; len <= n; ++len)
{
for (int left = 1; left <= n - len + 1; ++left)
{
int right = left + len - 1;
for (int k = left; k <= right; ++k)
dp[left][right] = max(dp[left][right], nums[left-1]*nums[k]*nums[right+1] + dp[left][k-1] + dp[k+1][right]);
}
}
return dp[1][n];
}
};