[leetcode] 312. Burst Balloons

Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by array nums. You are asked to burst all the balloons. If the you burst balloon i you will get nums[left] * nums[i] * nums[right] coins. Here left and right are adjacent indices of i. After the burst, the left and right then becomes adjacent.

Find the maximum coins you can collect by bursting the balloons wisely.

Note: 
(1) You may imagine nums[-1] = nums[n] = 1. They are not real therefore you can not burst them.
(2) 0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100

Example:

Given [3, 1, 5, 8]

Return 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

这道题非常有意思,想到了《飞屋环游记》,题目难度为Hard。

看完题目,最直观的想法是用回溯法逐个选择气球引爆,这种方法相当于给所有气球做了一次全排列,时间复杂度会达到O(n!),这么高的时间复杂度大集合测试肯定是过不了的,就不尝试了。

题目复杂的地方在于选择一个气球引爆后分成的子问题会互相关联,如果这样正向思考问题会变得相当复杂。这条路走不通,我们可以逆向思考,依据最后一个气球将问题划分为左右独立的两部分,因为左右两部分的left和right在这种情况下是确定的,所以左右两部分互相独立,这样通过分治法就可以把问题解决了。能否想到这里成为了解决本题的关键,这种逆向思考的方法也是很多问题中常用到的。通过遍历所有的气球,将其逐个作为最后一个引爆的气球计算最后的硬币数,选择其中最大的就得到了最终的结果。同时我们注意到,在采用分治法解决问题的时候,有很多相同的子区间会重复计算到,因此可以把子区间的计算结果缓存起来来优化计算。具体代码:

class Solution {
    int getMaxCoins(const vector<int>& nums, vector<vector<int>>& cnt, int bgn, int end, int left, int right) {
        if(bgn > end) return 0;
        if(cnt[bgn][end]) return cnt[bgn][end];
        
        int maxCn = 0;
        for(int i=bgn; i<=end; i++) {
            int curCn = 0;
            curCn += nums[i]*left*right;
            curCn += getMaxCoins(nums, cnt, bgn, i-1, left, nums[i]);
            curCn += getMaxCoins(nums, cnt, i+1, end, nums[i], right);
            maxCn = max(maxCn, curCn);
        }
        cnt[bgn][end] = maxCn;
        
        return maxCn;
    }
public:
    int maxCoins(vector<int>& nums) {
        int sz = nums.size();
        vector<vector<int>> cnt(sz, vector<int>(sz, 0));
        return getMaxCoins(nums, cnt, 0, sz-1, 1, 1);
    }
};
既然会有很多重复的子区间,为什么不采用动态规划呢?上面分治法采用自顶向下的方法来分割问题,动态规划则采用自底向上的方法来一步步获取最终的结果。这里我们用二维数组coins来存储优先引爆各区间气球时能获得的最大硬币数,既然优先引爆该区间的气球,那这个区间内最后引爆的气球的left和right就确定了,根据上面同样的方法就可以获得该区间的结果。这样把区间长度从0依次增加到气球数减1,就能够递推出最后的结果。具体代码:
class Solution {
public:
    int maxCoins(vector<int>& nums) {
        int sz = nums.size(), k = 0;
        if(!sz) return 0;
        vector<vector<int>> coins(sz, vector<int>(sz, 0));
        while(k < sz) {
            for(int i=0; i+k<sz; ++i) {
                int left = i ? nums[i-1] : 1;
                int right = (i+k!=sz-1) ? nums[i+k+1] : 1;
                for(int j=i; j<=i+k; ++j) {
                    int maxCoinsNum = 0;
                    if(j != i) maxCoinsNum += coins[i][j-1];
                    if(j != i+k) maxCoinsNum += coins[j+1][i+k];
                    maxCoinsNum += nums[j]*left*right;
                    coins[i][i+k] = max(coins[i][i+k], maxCoinsNum);
                }
            }
            ++k;
        }
        return coins[0][sz-1];
    }
};
另外看了题目作者的解答,思路差不多,讲解也非常清晰,大家可以看一下,文章最后会给出他的解答链接。代码写的比较简洁,里面增加了去除0的操作,同时在数组首尾加入1,减少了一些判断,大家可以学习下他的代码。具体代码:
int maxCoinsDP(vector<int> &iNums) {
    int nums[iNums.size() + 2];
    int n = 1;
    for (int x : iNums) if (x > 0) nums[n++] = x;
    nums[0] = nums[n++] = 1;


    int dp[n][n] = {};
    for (int k = 2; k < n; ++k) {
        for (int left = 0; left < n - k; ++left)
            int right = left + k;
            for (int i = left + 1; i < right; ++i)
                dp[left][right] = max(dp[left][right],
                     nums[left] * nums[i] * nums[right] + dp[left][i] + dp[i][right]);
        }

    return dp[0][n - 1];
}
题目作者的解答链接: https://leetcode.com/discuss/72216/share-some-analysis-and-explanations

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值