在问 · 算法题解

312. 戳气球

题目描述

有 n 个气球排成一排,编号为 0 到 n - 1,第 i 个气球上写着数字 nums[i]。

你需要把所有气球都戳破。戳破一个气球时,得到的硬币数等于它自己的数字乘以左右两侧相邻气球上的数字。这里的相邻指的是当前还没被戳破的气球;如果某一侧已经没有气球,就把那一侧当成数字为 1 的气球。

可以自由安排戳破的顺序。返回能得到的硬币总数的最大值。

示例 1
输入:nums = [3,1,5,8]
输出:167

解释:按 1、5、3、8 的顺序戳:3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 15 + 120 + 24 + 8 = 167。

示例 2
输入:nums = [1,5]
输出:10

解释:先戳 1 得 1*1*5 = 5,再戳 5 得 1*5*1 = 5,共 10。

数据范围

  • n == nums.length
  • 1 <= n <= 300
  • 0 <= nums[i] <= 100

解题思路

正向想“先戳哪个”会改变邻居关系,难以分解。反过来想“最后戳哪个”:在两端补 1 得到 a,定义 dp[i][j] 为只戳破开区间 (i,j) 内所有气球的最大硬币。若 k 是区间内最后戳破的,那时它的邻居一定是 a[i] 和 a[j],得到 a[i]*a[k]*a[j],而左右两段 (i,k)、(k,j) 互不影响。按区间长度从小到大枚举。

为什么正确

计算 dp[i][j] 时,所有更短的区间 dp[i][k]、dp[k][j] 都已求好。

复杂度分析

时间 O(n^3),空间 O(n^2)。

易错点

枚举的是“最后戳破”的气球而不是第一个;循环顺序必须按区间长度递增,否则会用到未计算的子区间。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def maxCoins(self, nums: List[int]) -> int:
        a = [1] + nums + [1]
        n = len(a)
        dp = [[0] * n for _ in range(n)]
        for length in range(2, n):
            for i in range(n - length):
                j = i + length
                for k in range(i + 1, j):
                    dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + a[i] * a[k] * a[j])
        return dp[0][n - 1]

载入编辑器,自己跑一遍 →

动态规划 专题的其他题目

查看 动态规划 全部题目 →