题目
有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。
这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1
超出了数组的边界,那么就当它是一个数字为 1 的气球。求所能获得硬币的最大数量。
思路
区间dp:
定义 f[i][j]
为考虑将 (i,j)
范围内(不包含 i
和 j
边界)的气球消耗掉,所能取得的最大价值。
考虑在开区间内最后一个被戳破的气球为k
,即k
是这个区间内最后一个被戳破的气球,换句话说,目前剩下的气球就是只剩下开区间的首尾i
和j
,以及最后一个被戳破的气球k
了
假设 dp[i][j]
表示开区间 (i,j)
内能拿到的最多金币 那么这个情况下 ,在 (i,j)
开区间得到的金币可以由 dp[i][k]
和 dp[k][j]
进行转移,如果此时选择戳爆k
气球,那么得到的金币就是total = dp[i][k] + val[i] * val[k] * val[j] + dp[k][j]
,其中val[i]
表示 i
位置气球的数字
也正是因为 k
是最后一个被戳爆的,所以 (i,j)
区间中 k
两边的东西必然是先各自被戳爆了的, 左右两边互不干扰
最后在 (i,j)
区间中选取的k可以是多个,进行一个枚举,从中选择使得 total
值最大的即可用来更新 dp[i][j]
,然后从 (i,j)
开区间只有三个数字的时候开始计算,储存每个小区间可以得到金币的最大值 然后慢慢扩展到更大的区间,即j
不断扩大,利用小区间里已经算好的数字来算更大的区间即可
java代码如下:
class Solution{
public int maxCoins(int[] nums){
int n = nums.length;
//创建一个辅助数组,在首尾各添加1,方便处理边界问题
int[] temp = new int[n + 2];
temp[0] = 1;
temp[n+1] = 1;
//填充原数组的值
for(int i = 0; i < n; i++){
temp[i + 1] = nums[i];
}
int[][] dp = new int[n + 2][n + 2];
//len表示开区间长度,从(i,k,j)开始循环,逐渐扩大
for(int len = 3; len <= n + 2; len++){
for(int i = 0; i <= n + 2 - len; i++){
int res = 0;
//k为开区间(i,j)内的索引
for(int k = i + 1; k < i + len - 1; k++){//范围不断扩大
int left = dp[i][k];//表示区间(i,k)
int right = dp[k][i + len -1];//表示区间(k,i + len - 1)
res = Math.max(res,left + temp[i] * temp[k] * temp[i + len - 1] + right);
}
dp[i][i + len - 1] = res;//不断记录开区间的最大金币
}
}
return dp[0][n+1];
}
}