题目
有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。
求所能获得硬币的最大数量。
示例 1:
输入:nums = [3,1,5,8]
输出: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
示例 2:
输入:nums = [1,5]
输出:10
思考过程
这道题如果思考整个过程会很麻烦,因为我们不知道戳破某个气球后,谁跟谁挨着,也就没办法求这么简单的公式:a*b*c.
不妨反过来想,如果有两个气球没别戳破,那么这两个气球中间的所有气球多有可能跟他们相连,形成a,b,c这种结构,所以这道题就变成了两个气球固定的情况下,选他俩之间哪个气球作为新插入的气球,让这两个气球求得最终和最大。
加入当前选择的是k位置气球加入
memo[left][right]=Math.max(memo[left][rigth],data[left]*data[right]*data[k]+memo[left][k]+memo[k][right]);
memo[left][k] 这个的含义是在pos>=left+1& pos <k-1这个位置中,能够获得的最大和。
memo[k][right] 这个的含义是在>=left+1 &pos<right这个位置中,能够获得公式计算之后的最大和。
所以这道题有两种解题思路
1、从上到下的记忆化搜索算法。
2、从下到上的dp算法。
一、记忆化搜索算法
这所以要用记忆化搜索算法就是为了避免重复计算超时,在我们计算过得位置用记忆数据计算该区域的最大值,如果该值已经计算过就直接返回。
记忆化搜索算法是一个从上到下的算法,我们第一步要确定总的计算范围,
也就是(0,n-1)
然后按照从上下的原则拆分到最小步骤,
1、如果开始,结束位置之间没有气球,则直接返回0
2、如果这中间夹杂着一个气球,则求这个气球的值(a[i-1]*a[i]*a[i+1]),返回
3、如果其中夹杂着多个气球,那么就求每一个气球的公式值.自求(left,k),(k,right)这两部分的和相加,就是最终添加第k个气球后最大值,而memo[left][right]就是再求调整该添加的球位置调整的情况下,最终获得的最大结果。
int[] data;
int[][] memo;
public int maxCoins(int[] nums) {
//方面计算扩展两个位置,开头和结尾都加一个1
int n = nums.length + 2;
data = new int[n];
data[0] = data[n - 1] = 1;
for (int i = 0; i < nums.length; i++) {
data[i + 1] = nums[i];
}
//记忆化搜索方程,用于存放已经计算好的最大和。
memo = new int[nums.length + 2][nums.length + 2];
for (int i = 0; i < nums.length; i++) {
Arrays.fill(memo[i], -1);
}
return solve(0, n - 1);
}
private int solve(int begin, int end) {
// 中间不夹球
if (begin + 1 >= end) {
return 0;
}
//已经计算过这个范围的最大和,直接返回。
if (memo[begin][end] != -1) {
return memo[begin][end];
}
//中间夹了很多球
for (int i = begin + 1; i < end; i++) {
//首先计算夹了这个求之后和的变化。
int sum = data[begin] * data[i] * data[end];
//计算不夹这个求的时候这个范围最大和。
sum += solve(begin, i) + solve(i, end);
//每次求得的值,获取最大的给该区域的记忆化数组
memo[begin][end] = Math.max(memo[begin][end], sum);
}
//到这里,所有的夹球位置都计算过了,也就是知道了begin,end范围的最大值了。
return memo[begin][end];
}
二、dp算法
看记忆化搜索之后,我们发现如果从底向上,也是能够获得了,其中记忆化搜索方法的memo就是dp的方程式,其表示某个范围最大的方程式和。
dp的思想就是从小范围向大范围扩展,一开始这个范围没有求,值给sum贡献0,然后
求夹一个球的总和,
求夹两个球的总和。
.............
dp[i][j]=Math.max(dp[i][j],dp[i+1][k]+dp[k][j]+data[k-1]*data[k]*data[k+1]);
public int maxCoins1(int[] nums) {
int n = nums.length + 2;
data = new int[n];
data[0] = data[n - 1] = 1;
for (int i = 0; i < nums.length; i++) {
data[i + 1] = nums[i];
}
//dp方程,初始化每一个位置为0
memo = new int[nums.length + 2][nums.length + 2];
for (int i = 0; i < nums.length; i++) {
Arrays.fill(memo[i], 0);
}
//从夹球数最少,到夹球数最多这个方向遍历
for(int left=nums.length-1;left>=0;left--){
for(int right=left+2;right<=nums.length+1;right++){
//遍历每一个新加入的求,为区间贡献的sum值,sum要再加上其之外的区间贡献的值
//才是完整的该区域的和是什么。不要漏掉其余区间的和。
//可以理解为新加入的求的贡献+没有它的时候该区间被分成左右两部分的时候和
//这两个值才是区间的完整和。
for(int k=left+1;k<right;k++){
int sum =data[left]*data[right]*data[k] ;
sum+=memo[left][k]+memo[k][right];
memo[left][right]=Math.max(memo[left][right],sum);
}
}
}
return memo[0][n-1];
}
复杂度分析
设小球的个数为n,则时间复杂度为O(n*n*n)
空间复杂度为O(n*n)