312. 戳气球
有 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
示例:
1 | 输入: [3,1,5,8] |
为了避免讨论边界情况,我们将 nums 数组首尾各加上 一个 数字 1 ,将这个新数组叫做 val。
方法一:记忆化搜索
戳气球这个操作会使两个气球从不相邻变为相邻,使得后续操作难以进行,我们倒过来看这些操作,将全过程看作是每次添加一个气球。
定义方法 solve,令 solve(i, j)
表示开区间(i, j)
内位置全部填满能够得到的最多硬币数。由于是开区间,因此区间两端的气球编号就是 i 和 j,对应 val[i] 和 val[j]。
- 当
i >= j - 1
时,开区间中没有气球,solve(i, j)
的值为 0 。 - 当
i < j - 1
时,我们枚举开区间(i, j)
内的全部位置 mid,令 mid 为当前区间第一个添加的气球,此时,区间中只有 三个值 val[i],val[mid],val[j],能得到的硬币数为这三个值的积。mid 将开区间(i, j)
分为(i, mid)
和(mid, j)
两部分,我们递归地计算这两个部分对solve(i, j)
的贡献。
$$
solve(i, j) = \max^{j-1}_{mid=i+1}{val[i]\times val[mid]\times val[j] + solve(i,mid) + solve(mid,j) }\ \ {i < j - 1}
$$
$$
solve(i, j) = 0 \ \ {i \geq j - 1}
$$
为了避免重复计算,我们储存 solve 的结果。
1 | class Solution { |
时间复杂度O(n^3),空间复杂度O(n^2)。
方法二:动态规划
我们可以将自顶向下的记忆化搜索变为自底向上的动态规划,令 dp[i][j]
表示填满开区间(i, j)
能得到的最多硬币数,边界条件为 i >= j - 1,此时dp[i][j] = 0
。
$$
dp[i][j]=\max^{j-1}_{k=i+1}{val[i]\times val[k]\times val[j] + solve(i,k) + solve(k,j) }\ \ {i < j - 1}
$$
$$
dp[i][j] = 0 \ \ {i \geq j - 1}
$$
最终答案为dp[0][n + 1]
。
1 | class Solution { |
时间复杂度O(n^3),空间复杂度O(n^2)。