leetCode刷题归纳-Divide and Conquer(312. Burst Balloons)

来源:互联网 发布:java什么叫并发 编辑:程序博客网 时间:2024/05/21 08:57

这个是比较久之前写的题了,今天刚好复习又看到。
虽然被归类到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范围结果    }};
0 0