题目
有 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
链接(中文版):https://leetcode-cn.com/problems/burst-balloons
链接(英文版):https://leetcode.com/problems/burst-balloons
分析
根据题目说明,先在两边各加一个1,即:1 3 1 5 8 1
如果最后戳破的气球是1,则蓝的3 5 8已经被戳破了,即:1 3 1 5 8 1,此时戳破1得到的分数是:戳破左边的3得到的最高分数+戳破右边的5 8得到的最高分数+(1 * 1 * 1)。根据动态规划的思想,戳破左边的3得到的最高分数和戳破右边的5 8得到的最高分数都已经事先计算得到了。
用如下6*6的矩阵M表示最优结果,黑色的数字是行号和列表。
M[0][2](红色X)表示把序号0到2之间的气球戳破的最高分数,即上述戳破左边的3得到的最高分数。
M[2][5](绿色X)表示把序号2到5之间的气球戳破的最高分数,即上述戳破右边的5 8得到的最高分数。
我们需要的最终答案是M[0][5](黑色的X),即戳破序号0到5之间的气球戳破的最高分数
0 | 1 | 2 | 3 | 4 | 5 | |
0 | X | X | ||||
1 | ||||||
2 | X | |||||
3 | ||||||
4 | ||||||
5 |
矩阵M初始化为全零,接下来一步一步完成和这个矩阵的填充。
戳破1个气球
需要从无法再细分的子问题开始计算,即只戳破一个气球的时候,上述戳破左边的3得到的最高分数就是一个无法在细分的子问题。这个结果可以直接计算出来,即M[0][2]=1*3*1=3。
然后M[1][3]=3*1*5=15,M[2][4]=1*5*8=40,M[3][5]=5*8*1=40,此时矩阵内容如下。
0 | 1 | 2 | 3 | 4 | 5 | |
0 | 3 | |||||
1 | 15 | |||||
2 | 40 | |||||
3 | 40 | |||||
4 | ||||||
5 |
戳破2个连续的气球
然后计算戳破2个连续的气球的最高分数M[0][3]、M[1][4]和M[2][5]。
M[0][3]=max(M[0][3], nums[0]*nums[i]*nums[3]+M[0][i]+M[i][3]),i的取值是1和2。M[0][3]初始是0。当i=1时,表示最后戳破编号为1的气球,此时区间0到3之间其余的气球已经都戳破了,就剩编号为1的气球了,因此得分是nums[0]*nums[1]*nums[3]。然后加上已经戳完的左边的气球最佳得分M[0][1](编号0和1之间有0个气球,因此M[0][1]=0,这个是初始化得到的)以及已经戳完的右边的气球最佳得分M[1][3](M[1][3]=15是上文计算得到的)。
因此当i=1时,M[0][3]=max(0, 1*3*5+0+15)=30。
同理当i=2时,M[0][3]=max(M[0][3], nums[0]*nums[2]*nums[3]+M[0][2]+M[2][3])=max(30, 1*1*5+3+0)=max(30, 8)=30。
接着求出其余的M[1][4]和M[2][5],此时矩阵内容如下。
0 | 1 | 2 | 3 | 4 | 5 | |
0 | 3 | 30 | ||||
1 | 15 | 135 | ||||
2 | 40 | 48 | ||||
3 | 40 | |||||
4 | ||||||
5 |
戳破3个连续的气球
然后计算戳破3个连续的气球的最高分数M[0][4]和M[1][5],矩阵内容如下。
0 | 1 | 2 | 3 | 4 | 5 | |
0 | 3 | 30 | 159 | |||
1 | 15 | 135 | 159 | |||
2 | 40 | 48 | ||||
3 | 40 | |||||
4 | ||||||
5 |
戳破4个连续的气球
然后计算戳破4个连续的气球的最高分数M[0][5],矩阵内容如下。黑色的167即为最终结果。
0 | 1 | 2 | 3 | 4 | 5 | |
0 | 3 | 30 | 159 | 167 | ||
1 | 15 | 135 | 159 | |||
2 | 40 | 48 | ||||
3 | 40 | |||||
4 | ||||||
5 |
代码
import numpy as np
class Solution:
def maxCoins(self, nums: List[int]) -> int:
nums = [1] + nums + [1]
n = len(nums)
M = np.zeros((n, n), dtype=np.int)
for k in range(2, n): #k=2时是计算戳破1个气球的最优解,k=3时事计算戳破2个连续的气球的最优解。。。
for a in range(0, n-k): #a是被戳破的连续K-1个气球的区间的左边,a+k是区间的右边
for i in range(a+1, a+k): #i就是上述分析中的i
M[a][a+k] = max(M[a][a+k], M[a][i] + M[i][a+k] + nums[a] * nums[i] * nums[a+k])
return M[0][-1]
总结
这道题和矩阵连乘很像,关键是找出计算子问题的顺序。