动态规划:戳气球——有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中...

一、题目描述

有 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

示例:

输入: [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

来源:力扣(LeetCode)  链接:https://leetcode-cn.com/problems/burst-balloons

二、题解 

给定四个球分别为[3,1,5,8],其实可以看做数组[1,3,1,5,8,1];

我们令dp[start][end]表示从start到end之间的气球戳破之后能得到的最大值,则本题就是求解dp[0][5],即戳破3,1,5,8四个气球能得到的最大值。

为了求解dp[0][5],我们要先得到递推公式,我们假设最后戳破的是第i个气球,则得到的值为

dp[0][5]=dp[0][i]+dp[i][5]+nums[0]*nums[i]*nums[5];

**即求得递推公式 **
dp[start][end]=max(dp[start][i]+dp[i][end]+nums[start]*nums[i]*nums[end])

1. 值得注意的是我们是假设最后戳破的是第i个气球,而不是第一个戳破的是第i个气球,因为根据题目我们可以知道相连的气球之间是有关联性的。假设最后戳破第i个气球才能满足之前戳破的气球中,i左边的跟i右边的不会相邻,也就是dp[start][i]和dp[i][end]才有效。

所以我们可以构造动态规划表:

2.其中0开始0结尾的气球序列的值一定为0,0开始1结尾的气球序列的值也为0.这是因为我们只能戳破边界值以外的数。想象一下,我们有(0,...i..., n-1)这样的序列,假设最后戳破的是下标为i的气球,所以之前戳破的气球一定都在(0,i), (i,n-1)中,注意这里没有使用[0,i]这样的符号,是因为之前戳破的气球一定不是i(i是最后戳破的),也一定不是0和n-1,因为0和n-1是人为加上去的,戳破了没有意义,所以只能是开区间内的值,而(0,0),(0,1)没有区间内的气球可以戳破,所以它的硬币值为0。

3.此外我们得到的递推公式是:

dp[start][end]=max(dp[start][i]+dp[i][end]+nums[start]*nums[i]*nums[end])

其中start<i<end,这意味着动态规划表中的值只能等在它下面的和在它左边的值计算完,它才能计算。所以动态规划表的构造顺序是下面这样的。

所以相应的for循环应该是下面这样的:

class Solution {
public:
    int maxCoins(vector<int>& nums) {
        nums.push_back(1);
        nums.emplace(nums.begin(), 1);
        int dp[nums.size()][nums.size()];
        for(int i=0;i<nums.size();i++)
        {
            dp[i][i] = 0;
            if(i<nums.size()-1)
                dp[i][i+1]=0;
        }
        for(int start=nums.size()-3;start>=0;start--)
        {
            for(int end=start+2;end<nums.size();end++)
            {
                int max = 0;
                for(int k=start+1;k<end;k++)
                {
                    int temp = dp[start][k]+dp[k][end]+nums[start]*nums[k]*nums[end];
                    if(temp>max)
                        max = temp;
                }
                dp[start][end]= max;
            }
        }
        return dp[0][nums.size()-1];
    }
};

进一步,可以简化成下面这样(主要是开始start的值不一样)

class Solution {
public:
    int maxCoins(vector<int>& nums) {
        nums.push_back(1);
        nums.emplace(nums.begin(), 1);
        int dp[nums.size()][nums.size()];
        for(int start=nums.size()-1;start>=0;start--)
        {
            for(int end=start;end<nums.size();end++)
            {
                int max = 0;
                for(int k=start+1;k<end;k++)
                {
                    int temp = dp[start][k]+dp[k][end]+nums[start]*nums[k]*nums[end];
                    if(temp>max)
                        max = temp;
                }
                dp[start][end]= max;
            }
        }
        return dp[0][nums.size()-1];
    }
};

三、总结

1. 对于动态规划问题,可以用反向的思维思考问题,结合分治的思想,解决问题。

2. 要界定好动态规划表的一些初始值。

3. 要根据状态转移公式确定依赖关系,从而确定构造动态规划表的顺序。

四、参考资料

https://leetcode-cn.com/problems/burst-balloons/solution/chuo-qi-qiu-dong-tai-gui-hua-wen-ti-by-lidada/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值