312戳气球(动态规划,区间动态规划)
作者:互联网
1、题目描述
有 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
2、示例
输入: [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
3、题解
基本思想:动态规划,这是一道区间动态规划,用的是分治的思想,大区间用小区间更新,很容易想到dfs+记忆化,这里用自底向上dp推出来的。石子合并和Floyd算法都是一类题。
dp[i][j]表示在开区间(i,j)范围内结果的最大值。dp[i][j]表示i~j最大值,i,j不戳破。以两个数作为左右端点,找出最优解中它们中间那个戳破的气球,中间这个气球把整个队列分为了2部分,要想让中间这个气球和2个端点靠在一起,就需要先把分开的2部分的气球戳破。比如k气球在i,j之间时(i,k,j)被戳破,那么要先戳破i,k、k,j之间的气球,所以dp[i][j]=dp[i][k]+dp[k][j]+nums[i]*nums[k]*nums[j]。开区间的意思是,我们只能戳爆 i 和 j 之间的气球,i 和 j 不要戳。k是这个区间最后一个被戳爆的气球。因为是最后一个被戳爆的,所以它周边没有球了,只有这个开区间首尾的 i 和 j 了。
举个例子,对于区间 0 到 4 之间的结果,递归过程是:
dp[0][4] =max { dp[0][1]+dp[1][4]+nums[0]*nums[1]*nums[4] , dp[0][2]+dp[2][4]+nums[0]*nums[2]*nums[4] , dp[0][3]+dp[3][4]+nums[0]*nums[3]*nums[4] }
dp[1][4]、dp[0][2]、dp[2][4]、dp[0][3]没有达到回归条件,会继续向下分解,以 dp[1][4] 为例:
dp[1][4]= max { dp[1][2]+dp[2][4]+nums[1]*nums[2]*nums[4] , dp[1][3]+dp[3][4]+nums[1]*nums[3]*nums[4] }
dp[2][4]、dp[1][3]继续分解:
dp[2][4]= dp[2][3] + dp[3][4] + nums[2]*nums[3]*nums[4]
dp[1][3] = dp[1][2] + dp[1][3] + nums[1]*nums[2]*nums[3]
到这里因为已经分解到了最小子问题,最小子问题会带着它们的解向上回归,也就是说我们的回归过程是:dp[3][4] , dp[2][3] , dp[2][4] , dp[1][2] , dp[1][3] , dp[1][4] , dp[0][1] , dp[0][2] , dp[0][3] , dp[0][4] 。因为 dp[i][j] 依赖的是 dp[i][k] 与 dp[k][j] 其中 i < k < j ,也就是说如果要求解 dp[ i ][ j ] 依赖了 [ i ][ 0 ] 到 [ i ][ j-1 ] 以及 [ i+1 ][ j ] 到 [ j-1 ][ j ] 的值。那么我们在dp表中 i 从 length 递减到 0, j 从 i+1 递增到 j 推演即可。
#include<iostream>
#include<vector>
#include<algorithm>
#include<map>
#include<list>
using namespace std;
class Solution {
public:
int maxCoins(vector<int>& nums) {
//基本思想:动态规划,这是一道区间动态规划,用的是分治的思想,大区间用小区间更新,很容易想到dfs+记忆化,这里用自底向上dp推出来的。石子合并和Floyd算法都是一类题。
nums.push_back(1);
nums.insert(nums.begin(),1);
//dp[i][j]表示在开区间(i,j)范围内结果的最大值。dp[i][j]表示i~j最大值,i,j不戳破。
//以两个数作为左右端点,找出最优解中它们中间那个戳破的气球,中间这个气球把整个队列分为了2部分,
//要想让中间这个气球和2个端点靠在一起,就需要先把分开的2部分的气球戳破。
//比如k气球在i,j之间时(i,k,j)被戳破,那么要先戳破i,k、k,j之间的气球,
//所以dp[i][j]=dp[i][k]+dp[k][j]+nums[i]*nums[k]*nums[j]
vector<vector<int>> dp(nums.size(),vector<int>(nums.size(),0));
for(int i=nums.size()-2;i>=0;i--)
{
for(int j=i+2;j<nums.size();j++)
{
for(int k=i+1;k<j;k++)
{
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k][j]+nums[i]*nums[k]*nums[j]);
}
}
}
return dp[0][nums.size()-1];
}
};
int main()
{
vector<int> nums={3,2,1,2,3,2,1};
Solution solute;
cout<<solute.maxCoins(nums)<<endl;
return 0;
}
标签:include,nums,--,312,戳破,动态,规划,气球,dp 来源: https://blog.csdn.net/Revendell/article/details/111363139