【DP】LeetCode 312. 戳气球

题目链接

312. 戳气球

思路

参考动态规划套路解决戳气球问题

分析动态规划题目的时候只需要考虑最后一个阶段,因为所有的阶段转化都是相同的,考虑最后一个阶段容易发现规律

在数组的动态规划问题中,一般 dp[i] 都是表示以 nums[i] 为结尾的状态;dp[i][j] 分别表示 以 nums1[i]nums2[j] 为结尾的状态,以此类推

字符串也是个数组,是字符数组

很明显这是一道区间dp,区间dp最基本的思想就是将大区间拆分成多个小区间的组合求解,假如说我们有个区间 [i,j],中间有多个分割点 k1,k2,,km,那么区间dp的状态转移公式一般为:

dp[i][j]=f(dp[i][j],g(dp[i][k1],dp[k1][k2],,dp[km][j]))

预处理

n=nums.length

因为在本题中,对下标越界的处理视为与1相乘,所以我们在原数组 nums 的基础上创建一个新数组 balloons,相当于在 nums 两端多加了元素1

int[] balloons = new int[n + 2];
balloons[0] = balloons[n + 1] = 1;

表示状态

状态表示就是靠猜,但是会有猜的套路,一般都是通过最终结果和数组数量来猜

经过数据预处理,现在实际气球的索引从0到 n - 1 变为了 1 到 n,balloons[0]balloons[n+1] 相当于两个虚拟气球。

那么原问题可以随之改变:在一排气球 balloons 中,请你戳破气球 0 和气球 n + 1 之间的所有气球(不包括 0 和 n + 1),使得最终只剩下气球 0 和气球 n + 1 两个气球,最多能够得到多少分?

结合上文对区间dp的介绍,我们就让 dp[i][j] 表示开区间 (i,j) 中的能获得的最大分数

找状态转移方程

思考的方向是:大问题的最优解怎么由小问题的最优解得到

根据区间 dp 的套路,我们知道肯定是在区间中间找分割点 k 进行分割组合,以此来得到最终结果,而这个分割点在本题中就是被戳破的气球。所以我们就需要思考:(i,j) 中间最后被戳破的气球应该是哪个

但是我们并不知道最后被戳破的气球应该在哪个位置,我们就需要枚举分割点 k。根据状态的定义,如果最后一个被戳破的气球是 k,那么 dp[i][j] 的计算公式应该为:

dp[i][j]=max(dp[i][j],dp[i][k]+dp[k][j]+balloons[i]balloons[k]balloons[j])

结合下图会更好理解这个公式

image

我们已经得到状态转移方程了,那么下一步就是思考如何遍历。这里有一个技巧:根据 base case 和最终状态进行推导

什么意思呢?我们先把 base case 和最终状态在 DP table 上画出来,如下图

image

对于任一 dp[i][j],我们希望所有 dp[i][k]dp[k][j] 已经被计算,画在图上就是这种情况:

image

那么,为了达到这个要求,可以有两种遍历方法,要么斜着遍历:

image

要么从下到上从左到右遍历:

image

在之后的代码中,这两种遍历方式我都给出了代码

边界处理

很容易想到 dp 矩阵的对角线元素为0,即 dp[i][i]=0

代码

斜线遍历状态

class Solution {
    public int maxCoins(int[] nums) {
        // 两端创建虚拟气球
        int n = nums.length;
        int[] balloons = new int[n + 2];
        balloons[0] = balloons[n + 1] = 1;
        for(int i = 1; i < n + 1; i++){
            balloons[i] = nums[i - 1];
        }

        int[][] dp = new int[n + 3][n + 3];
        // 枚举区间长度
        for(int len = 1; len <= n + 2; len++){
            // 枚举起点
            for(int i = 0; i + len - 1 < n + 2; i++){
                // 枚举终点
                int j = i + len - 1;
                if(len == 1 || len == 2){
                    dp[i][j] = 0;
                }
                // 枚举分割点
                for(int k = i + 1; k <= j - 1; k++){
                    // 状态转移
                    dp[i][j] = Math.max(
                            dp[i][j],
                            dp[i][k] + dp[k][j] + balloons[i] * balloons[k] * balloons[j]
                    );
                }
            }
        }

        return dp[0][n + 1];
    }
}

从下到上遍历状态

class Solution {
    public int maxCoins(int[] nums) {
        // 两端创建虚拟气球
        int n = nums.length;
        int[] balloons = new int[n + 2];
        balloons[0] = balloons[n + 1] = 1;
        for(int i = 1; i < n + 1; i++){
            balloons[i] = nums[i - 1];
        }

        int[][] dp = new int[n + 3][n + 3];
        // 从下到上遍历状态
        for(int i = n; i >= 0; i--){
            for(int j = i + 1; j < n + 2; j++){
                for(int k = i + 1; k < j; k++){
                    dp[i][j] = Math.max(
                            dp[i][j],
                            dp[i][k] + dp[k][j] + balloons[i] * balloons[k] * balloons[j]
                    );
                }
            }
        }

        return dp[0][n + 1];
    }
}
posted @   Frodo1124  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
点击右上角即可分享
微信分享提示