戳气球

题目描述

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

现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1 或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。

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

示例

示例 1:

1
2
3
4
5
输入: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 = 167

示例 2:

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

提示

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

解题思路

这是一道经典的区间动态规划问题。

核心思想

本题使用逆向思维,从戳破最后一个气球开始考虑。

定义 dp[i][j] 表示:戳破开区间 (i, j) 之间所有气球能获得的最大硬币数(不包括 i 和 j)。

为了处理边界情况,我们在原数组两端各添加一个值为 1 的虚拟气球:

$$
\text{new_nums} = [1] + \text{nums} + [1]
$$

状态转移方程

假设最后戳破的是位置 k 的气球(i < k < j):

$$
dp[i][j] = \max_{i < k < j} { dp[i][k] + dp[k][j] + \text{new_nums}[i] \times \text{new_nums}[k] \times \text{new_nums}[j] }
$$

遍历顺序

  • 区间长度 len 从 2 开始递增到 n+1
  • 计算每个区间 dp[i][j] 的最大值

代码实现

1
2
3
4
5
6
7
8
9
10
11
class Solution:
def maxCoins(self, nums: List[int]) -> int:
nums = [1] + nums + [1]
n = len(nums)
dp = [[0]*n for _ in range(n)]
for length in range(2, len(nums)):
for i in range(0, 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] + nums[i] * nums[j] * nums[k])
return dp[0][n-1]