戳气球
1. 题目描述
有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。
求所能获得硬币的最大数量。[1]
你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。
0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100
问题分析
1.刚看题时,这道题确实比较难入手,不过按照一般的思路来,还是可以想到动态规划的。面对这样的问题,我们首先将问题抽象化:题目中戳破气球可表示为从数组中的某个下标找一位数。我们可以设一个包含戳完气球的数组下标的数组:checked=[];那么每从nums选一个数,就将相应的下标push到checked数组里面,直到选完所有的数为止。则,问题就转换成关于nums下标的排列组合,这个时候就可以联想到暴力枚举的方法。那么相应的时间复杂度为O(n!),而题中n的范围在0-500之间,可想而知,这种方法在这里是行不通的。那么就得另辟蹊径了。
2.动态规划的分治思想:动态规划的一般思想是将问题分成若干个子问题,找到父子问题之间的状态转移方程。对于不经常运用算法的人来说,立刻想到是有些困难的,那么如何去找状态转移方程呢??
我认为有以下几个要点:
其一,注意问题的可拆分性,题目中的问题可以表达成多个子问题。
其二,不可后向影响性,前一个问题的解不能依赖于后一个问题的解。
其三,可递归性。
当一个问题符合以上三种时,基本上可以运用动态规划来解决。我们以这个问题为例进行分析:
题目让我们求戳破所有气球可获得的最大硬币数,而气球则是数组下标表示,数组里面的内容则是具体的数。那么我们可以将这个数组表示为一个区间[0,nums.length-1],即在此区间戳破气球所能获得的最大硬币数,而根据题目所获得的硬币数表示为:nums[left]*nums[i]*nums[right],那么我们试着分析它的可拆分性,区间[i,j]之间的最大获得硬币数是多少??我们采用数学上常用的归纳法来进行分析:
假设nums是一个元素数组:则获得的最大硬币数为1*nums[0]*1;
假设nums是包含两个元素的数组:则获得的最大硬币数为:[0,1]:MAX(1*nums[0]*nums[1]+1*nums[1]*1,nums[0]*nums[1]*1+1*nums[0]*1);
假设nums是包含三个元素的数组:则获得的最大硬币数为:
[0,2]:MAX(1*nums[0]*nums[1]+1*nums[1]*nums[2]+1*nums[2]*1 , 1*nums[0]*nums[1]+nums[1]*nums[2]*1+1*nums[1]*1 , nums[0]*nums[1]*nums[2]+1*nums[0]*nums[2]+1*nums[2]*1 , nums[0]*nums[1]*nums[2]+nums[0]*nums[2]*1+1*nums[0]*1,
nums[1]*nums[2]*1+nums[0]*nums[1]*1+1*nums[0]*1,
nums[1]*nums[2]*1+1*nums[1]*nums[2]+1*nums[0]*1,
...
);
可知[i,j]=[i,c]+[c,j]+nums[i]*nums[c]*nums[j];
解题
var maxCoins = function(nums) {
nums.unshift(1);
nums.push(1);
var dp=[];
for(var i=0;i<nums.length;i++){
var tmp=[];
for(var j=0;j<nums.length;j++){
tmp[j]=0;
}
dp[i]=tmp;
}
for(var i=2;i<nums.length;i++){
for(var j=0;j<nums.length;j++){
var n=i+j;
if(n<nums.length){
for(var c=j+1;c<n;c++){
dp[j][n]=Math.max(dp[j][n],nums[j]*nums[c]*nums[n]+dp[j][c]+dp[c][n]);
}
}
}
}
return dp[0][nums.length-1];
};
题目来源
- https://leetcode-cn.com/problems/burst-balloons