其他分享
首页 > 其他分享> > 312戳气球(动态规划,区间动态规划)

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