leetcode刷题(77)——312. 戳气球

一、题目

有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。

现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。

求所能获得硬币的最大数量。

说明:

你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。
0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100

示例:

输入: [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

二、思路——动态规划

对于求最值问题,我们想到回溯法和动态规划,这里我们用动态规划来解决。

定义状态:

题目中说你可以假设 n u m s [ − 1 ] = n u m s [ n ] = 1 nums[-1] = nums[n] = 1 nums[1]=nums[n]=1,我们先把这两个边界加到数组中,形成一个新的长度为 n + 2 n + 2 n+2 的数组 points,其中 p o i n t s [ 0 ] = p o i n t s [ n + 1 ] = 1 points[0] = points[n + 1] = 1 points[0]=points[n+1]=1,那么我们要戳破的就是从 1 到 n n n 的所有气球。

我们要求的是戳破所有的气球能获得的硬币的最大数量,换句话说就是,戳破气球 0 和气球 n + 1 n + 1 n+1 之间的气球能获得的硬币的最大数量。据此我们可以定义状态, d p [ i ] [ j ] dp[i][j] dp[i][j] 表示戳破气球 i 和气球 j 之间(开区间,不包括 i 和 j)的所有气球能获得的硬币的最大数量

推导状态转移方程:

状态转移的过程其实就是我们在每一步怎么做选择的过程。现在要求 d p [ i ] [ j ] dp[i][j] dp[i][j],假设我们每一步都选择戳破能够获得硬币数量最大的那个气球,最后只剩下一个气球的时候,没得选只能戳破该气球。所以我们要考虑的是气球 i 和 气球 j 之间最后被戳破的气球是哪个,不妨设为 k。那么戳破气球 k 能得到的金币数为 p o i n t s [ i ] ∗ p o i n t s [ k ] ∗ p o i n t s [ j ] points[i] * points[k] * points[j] points[i]points[k]points[j],那 d p [ i ] [ j ] dp[i][j] dp[i][j] 的值就是: 戳 破 ( i , k ) 之 间 的 气 球 获 得 的 最 大 硬 币 数 + 戳 破 ( k , j ) 之 间 的 气 球 获 得 的 最 大 硬 币 数 + 戳 破 气 球 k 获 得 的 硬 币 数 戳破(i, k)之间的气球获得的最大硬币数 + 戳破 (k,j)之间的气球获得的最大硬币数 + 戳破气球 k 获得的硬币数 (i,k)+(k,j)+k d p [ i ] [ j ] = d p [ i ] [ k ] + d p [ k ] [ j ] + p o i n t s [ i ] ∗ p o i n t s [ k ] ∗ p o i n t s [ j ] dp[i][j] = dp[i][k] + dp[k][j] + points[i] * points[k] * points[j] dp[i][j]=dp[i][k]+dp[k][j]+points[i]points[k]points[j] 其中 i < k < j i <k < j i<k<j

意思就是,我们穷举 i < k < j i <k < j i<k<j,求出把每个 k 都看作最后一个戳破的气球时能获得的最大硬币数,选择硬币最多的方案。

遍历方向:

d p [ i ] [ j ] dp[i][j] dp[i][j] 所依赖的状态是 d p [ i ] [ k ] dp[i][k] dp[i][k] d p [ k ] [ j ] dp[k][j] dp[k][j],那么我们必须保证:在计算 d p [ i ] [ j ] dp[i][j] dp[i][j]时, d p [ i ] [ k ] dp[i][k] dp[i][k] d p [ k ] [ j ] dp[k][j] dp[k][j] 已经被计算出来了(其中 i < k < j)。我们的目标是求出戳破所有的气球能获得的最大硬币数,即最后返回 d p [ 0 ] [ n + 1 ] dp[0][n + 1] dp[0][n+1] 的值,由此看出 i 是从大到小的,j 是从小到大的。

边界条件:

数组初始化:当 j ≤ i + 1 j \leq i +1 ji+1 时, d p [ i ] [ j ] = 0 dp[i][j] = 0 dp[i][j]=0,因为此时 ( i , j ) (i,j) (i,j) 之间没有气球了。

三、代码

class Solution {
    public int maxCoins(int[] nums) {
        int n = nums.length;
        // 把两个虚拟气球添加到数组中
        int[] points = new int[n + 2];
        points[0] = points[n + 1] = 1;
        for(int i = 1; i <= n; i++){
            points[i] = nums[i - 1];
        }
        int[][] dp = new int[n + 2][n + 2];// 数组默认为0,不用再写初始化条件了
        for(int i = n; i >= 0; i--){// i从大到小
            for(int j = i + 1; j < n + 2; j++){// j从小到大
                // 最后戳破的是哪个?选择硬币数最大的方案
                for(int k = i + 1; k < j; k++){
                    dp[i][j] = Math.max(dp[i][j], dp[i][k] + dp[k][j] + points[i] * points[k] * points[j]);
                }
            }
        }
        return dp[0][n + 1];
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值