312. 戳气球

83 阅读3分钟

题目介绍

力扣312题:leetcode-cn.com/problems/bu…

image.png

动态规划

我们来看一个区间,这个区间的气球长这样

image.png 假设这个区间是个开区间,最左边索引 i,最右边索引 j 我这里说 “开区间” 的意思是,我们只能戳爆 i 和 j 之间的气球,i 和 j 不要戳   DP思路是这样的,就先别管前面是怎么戳的,你只要管这个区间最后一个被戳破的是哪个气球 这最后一个被戳爆的气球就是 k   注意!!!!!

k是这个区间   最后一个   被戳爆的气球!!!!!

k是这个区间   最后一个   被戳爆的气球!!!!!

假设最后一个被戳爆的气球是粉色的,k 就是粉色气球的索引,然后由于 k 是最后一个被戳爆的,所以它被戳爆之前的场景是什么亚子?

image.png

是这样子的朋友们!因为是最后一个被戳爆的,所以它周边没有球了!没有球了!只有这个开区间首尾的 i 和 j 了!!这就是为什么DP的状态转移方程是只和 i 和 j 位置的数字有关

假设 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 位置气球的数字,然后 (i,k) 和 (k,j) 也都是开区间   那你可能又想问了,戳爆粉色气球我能获得 val[i]*val[k]*val[j] 这么多金币我能理解(因为戳爆 k 的时候只剩下这三个气球了),但为什么前后只要加上 dp[i][k] 和 dp[k][j] 的值就行了呀?

因为 k 是最后一个被戳爆的,所以 (i,j) 区间中 k 两边的东西必然是先各自被戳爆了的, 左右两边互不干扰,大家可以细品一下

这就是为什么我们 DP 时要看 “最后一个被戳爆的” 气球,这就是为了让左右两边互不干扰,这大概就是一种分治的思想叭

所以你把 (i,k) 开区间所有气球戳爆,然后把戳爆这些气球的所有金币都收入囊中,金币数量记录在 dp[i][k],同理,(k,j) 开区间你也已经都戳爆了,钱也拿了,记录在 dp[k][j],所以你把这些之前已经拿到的钱 dp[i][k]+dp[k][j] 收着,再加上新赚的钱 val[i]*val[k]*val[j] 不就得到你现在戳爆气球 k 一共手上能拿多少钱了吗

而你在 (i,j) 开区间可以选的 k 是有多个的,见一开头的图,除了粉色之外,你还可以戳绿色和红色 所以你枚举一下这几个 k,从中选择使得 total 值最大的即可用来更新 dp[i][j]

然后呢,你就从 (i,j) 开区间只有三个数字的时候开始计算,储存每个小区间可以得到金币的最大值 然后慢慢扩展到更大的区间,利用小区间里已经算好的数字来算更大的区间 就可以啦!撒花!

代码如下:

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表示开区间长度
        for(int len = 3; len <= n+2; len++){
            // i表示开区间左端点
            for(int i = 0; i <= n+2-len; i++){
                int res = 0; 
                // k为开区间内的索引
                for(int k = i+1; k < i+len-1; k++){
                    int left = dp[i][k];
                    int right = dp[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];
    }
}