leetcode312. 戳气球
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/burst-balloons
题目描述
有 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 = 315 + 358 + 138 + 181 = 167
示例 2:
输入:nums = [1,5]
输出:10
提示:
n == nums.length
1 <= n <= 300
0 <= nums[i] <= 100
暴力递归
假设要打爆nums[L…R]这个范围上所有的气球,并且假设nums[L - 1]和nums[R + 1]的气球都没有被打爆,尝试的过程是process 函数,最后获得的最大分数为process(L,R).依次尝试,如果每个气球被打爆,具体为:
如果nums[L] 最后被打爆.也就是先把nums[L + 1,R]范围上的气球打完后,再打爆nums[L].先把nums[L + 1,R]上的气球打爆的最大分数为process(L + 1,R);因为此时nums[L + 1,R]上的气球都被打完了.所以nums[L] 左边是nums[L -1] ,右边是nums[R + 1],最后打爆L 气球的最大分数是:
nums[L - 1] * nums[L ] * nums[R + 1] + process(L + 1,R)
同理.如果nums[R]最后被打爆.可以得到最大分数是:
nums[L - 1] * nums[R] * nums[R + 1] + process(L,R- 1)
如果位于L 到 R 中间的某一个位置i被打爆i,那么就表示L 到 i-1位置和i+ 1 到R 位置上先被打爆,所以可以得出;最大分数是
process(L ,i - 1)+ process(i + 1,R) + nums[i] * nums[L -1] * nums[R + 1]
根据以上尝试方案,可以写出.暴力递归的尝试.
代码演示
/**
* 暴力递归 求L - R位置,的最大分数
* 假设 L -1 位置和R + 1 位置没有被打爆.
* @param nums
* @param L
* @param R
* @return
*/
public int process(int[] nums,int L,int R){
//base case L 到 R 位置上只剩一个气球时,直接打爆
if(L == R){
return nums[L - 1] * nums[L] * nums[R + 1];
}
//最后打爆L 和最后打爆R 的情况,选择一个最大值
int max = Math.max(nums[L - 1] * nums[L] * nums[R + 1] + process(nums,L+1,R),
nums[L - 1] * nums[R] * nums[R + 1] + process(nums,L,R - 1));
//L 到 R 之间的一个位置上,最大值
for (int i = L + 1;i < R;i++){
max = Math.max(max,
nums[L - 1] * nums[i] * nums[R + 1]
+ process(nums,L,i - 1)
+ process(nums,i + 1,R));
}
return max;
}
如果对nums 开头和结尾补上1,那么两个1 之间的位置就是我们要求的的答案,这样可以避免边界的判断
/**
* 打气球
* @param nums
* @return
*/
public int maxCoins(int[] nums) {
if (nums == null || nums.length == 0){
return 0;
}
if (nums.length == 1){
return nums[0];
}
int N = nums.length;
int[]help = new int[N + 2];
help[0] = 1;
help[N + 1] = 1;
for (int i = 0;i < N;i++){
help[i + 1] = nums[i];
}
// 1 到 N 就是我们要的答案
return process(help,1,N);
}
暴力递归加缓存
/**
* 带缓存的递归
* 记忆化搜索
* @param nums
* @return
*/
public int maxCoins(int[] nums) {
if (nums == null || nums.length == 0){
return 0;
}
if (nums.length == 1){
return nums[0];
}
int N = nums.length;
int[]help = new int[N + 2];
help[0] = 1;
help[N + 1] = 1;
for (int i = 0;i < N;i++){
help[i + 1] = nums[i];
}
int[][]dp = new int[N + 2][N + 2];
return process(help,1,N,dp);
}
/**
* 递归加缓存
* @param nums
* @param L
* @param R
* @param dp
* @return
*/
public int process(int[] nums,int L,int R,int[][]dp){
if(L == R){
return nums[L - 1] * nums[L] * nums[R + 1];
}
if(dp[L][R] != 0){
return dp[L][R];
}
int max = Math.max(nums[L - 1] * nums[L] * nums[R + 1] + process(nums,L+1,R,dp),
nums[L - 1] * nums[R] * nums[R + 1] + process(nums,L,R - 1,dp));
for (int i = L + 1;i < R;i++){
max = Math.max(max,
nums[L - 1] * nums[i] * nums[R + 1]
+ process(nums,L,i - 1,dp)
+ process(nums,i + 1,R,dp));
}
dp[L][R] = max;
return max;
}
动态规划
动态规划就是对递归的改写.三个步骤,
1,根据base case 初始化dp表
2.递归过程改成dp 表拿值得过程
3.返回递归调用得初始状态
/**
* 动态规划
* @param nums
* @return
*/
public int maxCoins2(int[]nums){
if (nums == null || nums.length == 0){
return 0;
}
if (nums.length == 1){
return nums[0];
}
int N = nums.length;
int[]help = new int[N + 2];
help[0] = 1;
help[N + 1] = 1;
for (int i = 0;i < N;i++){
help[i + 1] = nums[i];
}
int[][]dp = new int[N + 2][N + 2];
for (int i = 1;i <= N;i++){
dp[i][i] = help[i - 1] * help[i] * help[i + 1];
}
for (int L = N;L >= 1;L--){
for (int R = L + 1;R <= N;R++){
int finalL = help[L - 1] * help[L] * help[R + 1] + dp[L + 1][R];
int finalR = help[L - 1] * help[R] * help[R + 1] + dp[L][R - 1];
dp[L][R] = Math.max(finalR,finalL);
for (int i = L + 1;i < R;i++){
dp[L][R] = Math.max(dp[L][R],
help[L - 1] * help[i] * help[R + 1] +
dp[L][i - 1] + dp[i + 1][R]);
}
}
}
return dp[1][N];
}