Given n
balloons, indexed from 0
to n-1
. Each balloon is painted with a number on it represented by array nums
. You are asked to burst all the balloons. If the you burst balloon i
you will get nums[left] * nums[i] * nums[right]
coins. Here left
and right
are adjacent indices of i
. After the burst, the left
and right
then becomes adjacent.
Find the maximum coins you can collect by bursting the balloons wisely.
Note:
- You may imagine
nums[-1] = nums[n] = 1
. They are not real therefore you can not burst them. - 0 ≤
n
≤ 500, 0 ≤nums[i]
≤ 100
Example:
Input: [3,1,5,8]
Output:
Input: [3,1,5,8]
Output: 167
Explanation: 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
LeetCode:链接
先理解一个事情,就是把整个数列看做一个整体,那么最优解值,一定是最后一个气球i值(i 是未知的),加上以i为分界线,左右两个区间的最优解。不难理解,最后就剩下一个气球时,很显然,把两边的最优解加上最后一个气球值,就是整体的最优解。
问题:最后被戳破的气球i是哪一个?因为已经知道区间内的所有数字,那么就可以枚举这些数字,找到最优的那个i 。这类问题一般归为区间 dp 。
问题:左右小区间的最优解如何确定?那么从最小区间开始计算,通俗的讲,先计算长度2的小区间,然后依次增加区间的长度。每次计算时,小区间就像一个滑动窗口,例如从长度为2的小区间作为一个窗口,从左向右依次滑动,求出所有小区间的最优解保存到dp[l][r]中。
可以看出计算过程是自底向上的。所以dp[l][r]只用到了上三角的空间(l < r)。
(4)dp状态转换方程:
dp[left][right] = max(dp[left][right], nums[left] * nums[i] * nums[right] + dp[left][i] + dp[i][right])
dp[left][right],表示区间left-->right的最优解(不包括,left和right 两个边界)
nums[left] * nums[i] * nums[right],表示最后一个被戳破的气球的得分,为什么要乘上两个边界值?因为它是最后一个被戳破的气球,整体来看,最后就剩下一个,就是乘上两个1,在子区间内,就是乘上两边的边界值了。
max(dp[left][right], nums[left] * nums[i] * nums[right] + dp[left][i] + dp[i][right]),表示从区间内开始枚举i,计算出本次区间的最优解。
class Solution(object):
def maxCoins(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
# 清除为0的数字,因为0不会得分,然后首尾添加[1],方便计算
nums = [1] + [i for i in nums if i > 0] + [1]
n = len(nums)
# 初始化dp
dp = [[0] * n for i in range(n)]
# k 确定一个滑动窗口的大小,从2开始
for k in range(2, n):
# 滑动窗口,从左向右滑动,确定区间的开始(left)、结束(right)位置
for left in range(0, n-k):
right = left + k
# 开始枚举,区间内哪一个数字作为最后一个被戳破,使其得分最高
for i in range(left+1, right):
dp[left][right] = max(dp[left][right], nums[left] * nums[i] * nums[right] + dp[left][i] + dp[i][right])
return dp[0][n-1]