Leetcode 312. [312] 戳气球 动态规划
/* * @lc app=leetcode.cn id=312 lang=cpp * * [312] 戳气球 * * https://leetcode-cn.com/problems/burst-balloons/description/ * * algorithms * Hard (67.72%) * Likes: 715 * Dislikes: 0 * Total Accepted: 45.1K * Total Submissions: 66.6K * Testcase Example: '[3,1,5,8]' * * 有 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: * * * 输入: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: * * * 输入:nums = [1,5] * 输出:10 * * * * * 提示: * * * n == nums.length * 1 * 0 * * */
思路:
难点在于定于dp数组以及如何进行选择
这里需要对问题进行一个简单地转化。题目说可以认为nums[-1] = nums[n] = 1
,那么我们先直接把这两个边界加进去
那么我们可以改变问题:在一排气球points
中,请你戳破气球0
和气球n+1
之间的所有气球(不包括0
和n+1
),使得最终只剩下气球0
和气球n+1
两个气球,最多能够得到多少分?
现在可以定义dp
数组的含义:
dp[i][j] = x
表示,戳破气球i
和气球j
之间(开区间,不包括i
和j
)的所有气球,可以获得的最高分数为x
。
那么根据这个定义,题目要求的结果就是dp[0][n+1]
的值,而 base case 就是dp[i][j] = 0
,其中0 <= i <= n+1, j <= i+1
,因为这种情况下,开区间(i, j)
中间根本没有气球可以戳。
i
和j
就是两个「状态」,最后戳破的那个气球k
就是「选择」。
根据刚才对dp
数组的定义,如果最后一个戳破气球k
,dp[i][j]
的值应该为:
dp[i][j] = dp[i][k] + dp[k][j]
+ points[i]*points[k]*points[j]
你不是要最后戳破气球k
吗?那得先把开区间(i, k)
的气球都戳破,再把开区间(k, j)
的气球都戳破;最后剩下的气球k
,相邻的就是气球i
和气球j
,这时候戳破k
的话得到的分数就是points[i]*points[k]*points[j]
。
那么戳破开区间(i, k)
和开区间(k, j)
的气球最多能得到的分数是多少呢?嘿嘿,就是dp[i][k]
和dp[k][j]
,这恰好就是我们对dp
数组的定义嘛!
由于是开区间,dp[i][k]
和dp[k][j]
不会影响气球k
;而戳破气球k
时,旁边相邻的就是气球i
和气球j
了,最后还会剩下气球i
和气球j
,这也恰好满足了dp
数组开区间的定义。
那么,对于一组给定的i
和j
,我们只要穷举i < k < j
的所有气球k
,选择得分最高的作为dp[i][j]
的值即可,这也就是状态转移方程:
// 最后戳破的气球是哪个? for (int k = i + 1; k < j; k++) { // 择优做选择,使得 dp[i][j] 最大 dp[i][j] = Math.max( dp[i][j], dp[i][k] + dp[k][j] + points[i]*points[j]*points[k] ); }
对于任一dp[i][j]
,我们希望所有dp[i][k]
和dp[k][j]
已经被计算,画在图上就是这种情况:
class Solution { public: int maxCoins(vector<int>& nums) { int n=nums.size(); nums.insert(nums.begin(),1); nums.push_back(1); vector<vector<int>> dp(n+2,vector<int>(n+2,0)); for(int i=n;i>=0;--i){ for(int j=i+1;j<n+2;++j){ for(int k=i+1;k<j;++k){ int temp=dp[i][k]+nums[i]*nums[k]*nums[j]+dp[k][j]; dp[i][j]=max(dp[i][j],temp); } } } return dp[0][n+1]; } };