这个是比较久之前写的题了,今天刚好复习又看到。
虽然被归类到DC分类,但是比较明显的DP算法题(个人感觉),题目描述就不写了,直接上Example:
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
大概的意思就是:
有几只气球,每爆一个就得到这只气球和左右气球权重(不存在为1)相乘的coin,问按照什么样的顺序引爆才能获得最多的coin?
分析思路
DP编程的特点就是自下而上的思想(bottom-up),个人的理解就是,面对一个复杂的大问题,如何做才能将大问题拆解为一个个小规模的问题,而且这些小规模的问题一定是解决大规模问题的一部分。另外,DP编程时常伴随着构造二维数组而不是递归函数。话虽如此,但是怎么分解问题,怎么构造数组的横纵坐标,各自代表什么意义,则需要大量的算法经验。。。。。
个人对于DP算法的思路可以归纳为:自上而下的思考问题,自下而上地构造数组
具体的解释一下上一句话的含义,对于本题来说,以上文的例子为引:
nums = [3,1,5,8]
自上而下地思考问题
为了解决问题的方便,我们不妨将原数组改造一下,两边各添一个1,但是注意,两边的1都是我们添加上去的,不能选择,我们在这里设置两个边界指针,而且规定指针指向的值是不能被选择的:
1 — 3 — 1 — 5 — 8 — 1
left right
很多人都会想,先爆哪一个球?现在我们不妨将思路逆转一下,想想最后爆哪一个,假设我们选择最后爆最后一个气球8,那么现在的问题来了,在爆8之前我们爆哪一个呢?
有三个选择,3-1-5,不难发现,现在的问题转化为这样:
1 — 3 — 1 — 5 — 8
left right
因为8是最后爆的,所以不能选而且成为right,好我们现在选择5,同理:
1 — 3 — 1 — 5
left right
继续选择1:
1 — 3 — 1
left right
此时此刻已经没得选了,只能选择3,也就是说我们按照3-1-5-8的顺序来选择。最底层的情况是只剩下一个可以选的气球(或者说一开始就要选的气球),那么这样的情况有几种呢?
因为left-right不可以选,所以只有3-1-5-8这四个气球可以选,对应的情况分别是(左右两边的数字代表边界):
1 — 3 — 1
3 — 1 — 5
1 — 5 — 8
5 — 8 — 1
回到最初的问题,假设我们最初的选择是:最后爆1会怎么样?
问题转化为下面这样:
1 — 3 — 1
left1 right1
1 — 5 — 8 — 1
left2 right2
1-3-1是前面解决过的问题,1-5-8-1也是前面解决过的问题,而且这样的模式也很容易观察出分治的痕迹,现在我们要做的就是将left=0,right=5的问题转化为一个个的小问题,我们将这些小问题做一个整理分类,不难发现可以按照长度分成3类:
left | right | len(riht-left) |
---|---|---|
0 | 4 | 4 |
1 | 5 | 4 |
0 | 3 | 3 |
1 | 4 | 3 |
2 | 5 | 3 |
0 | 2 | 2 |
1 | 3 | 2 |
2 | 4 | 2 |
3 | 5 | 2 |
而且1-5可以转化为
2+(2-5)
(1-3)+(3-5)
(1-4)+4
三种情况,基于此我们可以构造二维数组dp[6][6],两个维度的坐标分别代表left和right,即以边界为i,j的区域所能爆炸得到的最大值,最后我们取dp[0][5]的值即为题目中所求的值(left=0,right=5),代码如下:
class Solution {
public:
int maxCoins(vector<int> &iNums) {
int nums[iNums.size()+2]; int n=1;
for(int tmp:iNums) nums[n++]=tmp;
nums[0]=nums[n++]=1;//这样处理数组的代码十分简洁!值得借鉴
vector<vector<int>>dp(n,vector<int>(n,0));
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[l][r]的意义就是以l-r为左右边界的最大爆炸值
dp[left][right]=max(dp[left][right],
nums[left]*nums[i]*nums[right]+dp[left][i]+dp[i][right]);
}
}
}
return dp[0][n-1];//bottom-up的DP算法,保留了许多中间数据(dp的特点就是空间消耗大,中间数据多) 最终得到了0->n-1范围结果
}
};