DP-LeetCode312. 戳气球

1、题目描述

https://leetcode-cn.com/problems/burst-balloons/

有 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

2、代码详解

(1)新问题定义

每戳破一个气球 nums[i],得到的分数和该气球相邻的气球 nums[i-1] 和 nums[i+1] 是有相关性的

子问题必须独立。对于戳气球问题,想用动态规划,必须巧妙地定义 dp 数组的含义,避免子问题产生相关性。

处理技巧:两个边界加虚拟气球

先直接把这两个边界加进去,形成一个新的数组 points,

现在气球的索引变成了从 1 到 n,points[0] 和 points[n+1] 可以认为是两个「虚拟气球」。

新问题:

在一排气球 points 中,请你戳破气球 0 和气球 n+1 之间的所有气球(不包括 0 和 n+1),使得最终只剩下气球 0 和气球 n+1 两个气球,最多能够得到多少分?

(2)dp定义

  • dp[i][j] = x 表示,戳破气球 i 和气球 j 之间(开区间,不包括 i 和 j)的所有气球,可以获得的最高分数为 x
  • 结果就是 dp[0][n+1] 的值
  • 而 base case 就是 dp[i][j] = 0,0 <= i <= n+1, j <= i+1,开区间 (i, j) 中间根本没有气球可以戳

(3)转移方程

如果正向思考,就只能回溯算法;反向思考,想一想气球 i 和气球 j 之间最后一个被戳破的气球可能是哪一个?所有气球都可能是最后被戳破的那一个,不防假设为 k。

i 和 j 就是两个「状态」,最后戳破的那个气球 k 就是「选择」。

最后一个戳破气球 k   , 

  • dp[i][j] = dp[i][k] + dp[k][j] + points[i]*points[k]*points[j]
  • 先把开区间 (i, k) 的气球都戳破,再把开区间 (k, j) 的气球都戳破;
  • 最后剩下的气球 k,相邻的就是气球 i 和气球 j,这时候戳破 k 的话得到的分数就是 points[i]*points[k]*points[j]。
  • 那么戳破开区间 (i, k) 和开区间 (k, j) 的气球最多能得到的分数就是 dp[i][k] 和 dp[k][j],这恰好就是对 dp 数组的定义!

由于是开区间,dp[i][k] 和 dp[k][j] 不会影响气球 k;

而戳破气球 k 时,旁边相邻的就是气球 i 和气球 j 了,最后还会剩下气球 i 和气球 j,这也恰好满足了 dp 数组开区间的定义。

对于任一 dp[i][j],希望所有 dp[i][k] 和 dp[k][j] 已经被计算,下图所示:

从下到上从左到右遍历

class Solution(object):
    def maxCoins(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        n = len(nums)
        # 添加两侧的虚拟气球
        points = [0] * (n + 2)
        points[0] = points[n + 1] = 1
        for i in range(1, n + 1):
            points[i] = nums[i - 1]
        dp = [[0] * (n + 2) for _ in range(n + 2)]  # base case 已经都被初始化为 0
        # i 应该从下往上
        for i in range(n, -1, -1):
            # j 应该从左往右
            for j in range(i + 1, n + 2):
                # 最后戳破的气球k
                for k in range(i + 1, j):
                    dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] +
                                   points[i] * points[j] * points[k])
        return dp[0][n + 1]

https://leetcode-cn.com/problems/burst-balloons/solution/dong-tai-gui-hua-tao-lu-jie-jue-chuo-qi-qiu-wen-ti/

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值