跳到主要内容

312. 戳气球

题目描述

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

现在要求你戳破所有的气球。戳破第 i 个气球,可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。这里 nums[-1]nums[n] 都视为 1

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

示例

示例 1:

输入:nums = [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 = 15 + 120 + 24 + 8 = 167

示例 2:

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

解题思路

第一步:理解问题本质

戳破气球的顺序会影响最终收益。关键问题是:按什么顺序戳破气球能获得最多硬币?

第二步:为什么正向思考困难

如果正向思考(先戳哪个气球),会发现戳破一个气球后,周围的边界会发生变化,状态很复杂。

核心转换:反过来想,考虑最后一个被戳破的气球。

第三步:逆向思维——区间DP

关键洞察

  • 在数组两端添加虚拟气球 1,这样每个真实气球被戳破时,左右边界都确定了
  • dp[i][j] 表示戳破 (i,j) 开区间内所有气球能获得的最大硬币数
  • 枚举 k(i,j) 区间内最后一个被戳破的气球

状态转移

dp[i][j] = max(dp[i][k] + dp[k][j] + nums[i] * nums[k] * nums[j]),其中 i < k < j

为什么正确

  • 最后一个被戳破的 k,其左右 (i,k)(k,j) 的气球已经被戳破
  • 此时戳破 k 获得的硬币 = nums[i] * nums[k] * nums[j]
  • 左右子问题互相独立,用加法原理

完整代码实现

from typing import List


class Solution:
"""
戳气球 - 区间动态规划

dp[i][j] 表示戳破 (i,j) 开区间内所有气球能获得的最大硬币数
在数组两端添加虚拟气球1,简化边界处理

时间复杂度:O(n^3)
空间复杂度:O(n^2)
"""

def maxCoins(self, nums: List[int]) -> int:
# 在数组两端添加虚拟气球1
nums = [1] + nums + [1]
n = len(nums)

# dp[i][j] 表示戳破 (i,j) 开区间内所有气球能获得的最大硬币数
dp = [[0] * n for _ in range(n)]

# 按区间长度从小到大枚举(长度至少为2才有中间元素)
for length in range(2, n):
for i in range(n - length):
j = i + length
# 枚举 (i,j) 区间内最后一个被戳破的气球 k
for k in range(i + 1, j):
dp[i][j] = max(
dp[i][j],
dp[i][k] + dp[k][j] + nums[i] * nums[k] * nums[j]
)

return dp[0][n - 1]

示例推演

nums = [3,1,5,8] 为例:

添加虚拟气球:[1, 3, 1, 5, 8, 1],n=6

区间长度为2(相邻两个虚拟气球之间只有一个真实气球)

(i,j)kdp[i][k]dp[k][j]nums[i]*nums[k]*nums[j]dp[i][j]
(0,2)100131=33
(1,3)200315=1515
(2,4)300158=4040
(3,5)400581=4040

区间长度为3

(i,j)kdp[i][k]dp[k][j]硬币总和
(0,3)1015135=1530
(0,3)230115=58
(1,4)2040318=2464
(1,4)3150358=120135
(2,5)3040151=545
(2,5)4400181=848

区间长度为4

(i,j)kdp[i][k]dp[k][j]硬币总和
(0,4)10135138=24159
(0,4)2340118=851
(0,4)380158=4048

| (1,5) | 2 | 0 | 48 | 311=3 | 51 | | (1,5) | 3 | 15 | 40 | 351=15 | 70 | | (1,5) | 4 | 135 | 0 | 381=24 | 159 |

区间长度为5(最终结果)

(i,j)kdp[i][k]dp[k][j]硬币总和
(0,5)10159131=3162
(0,5)2348111=152
(0,5)3840151=553
(0,5)41590181=8167

最终 dp[0][5] = 167


复杂度分析

解法时间复杂度空间复杂度说明
暴力枚举O(n!)O(n)枚举所有戳破顺序
区间DP(最优)O(n^3)O(n^2)枚举区间和分割点

易错点总结

1. 为什么枚举最后一个被戳破的气球

正向思考:先戳破一个气球,边界变化,状态复杂。

逆向思考:假设 k 是最后一个被戳破的,那么 k 的左右两边已经被戳破,边界固定为 nums[i]nums[j]

2. 区间是开区间

dp[i][j] 表示 (i,j) 开区间,即 ij 本身不戳破。这样 k 的范围是 i+1j-1

3. 虚拟气球

在两端添加 1,这样第一个和最后一个真实气球被戳破时也有确定的左右边界。


扩展思考

1. 空间优化

可以优化到 O(n),但代码会更复杂。

2. 与矩阵链乘法的关系

本题是区间DP的经典应用,与矩阵链乘法问题结构类似。


相关题目

加载评论中...