LeetCode 312. Burst Balloons 解题报告

来源:互联网 发布:集团军总司令源码程序 编辑:程序博客网 时间:2024/05/16 12:09

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.

(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


示例

Given [3, 1, 5, 8]

Return 167
Example


限制条件

没有明确给出.


解题思路

参考思路:

不得不说,这是我目前做过的自认为最难的一道题,不仅做的时候完全没有头绪,甚至看了discuss以及其他人的博客后都不怎么理解解题的思路以及为什么是这样实现代码。所以下面讲的,仅是我对于参考代码的一些理解而已,欢迎理解了整个解题思路的读者给我解惑。

我对参考代码的理解如下:
首先由于nums[-1]和nums[n]都为0,所以在解题时可以直接添加两个1在nums的首尾位置。
然后构造这么一个区间[nums[left], …, num[right]]。区间[nums[left], …, nums[right]]跟区间内元素 i 表示的意思是区间内除i,nums[left], nums[right]以外的元素都已经被弄破,此时轮到i被弄破。并且隐含了一个意思:nums[left]跟nums[right]如果是nums的元素,那么它们会在i之后才被弄破。
举个例子:
nums为[3, 2, 5, 8],处理后变成了[1, 3, 2, 5, 8, 1]。
假设区间是[1, 3, 2, 5],i=3。
那么表示2在3之前被弄破,3在5之前被弄破。

Ok,理解了区间[nums[left], …, num[right]]以及区间内元素i它们组合一起表示的含义后,不难发现区间的最小长度是3, 因为至少要包含nums[left], i,nums[right]这三个元素,而最大的长度就是nums.size() + 2。

那left跟right又该怎么理解呢?它们是nums的索引,指向的是某个i被弄破时的相邻两个元素。同样以上面的nums为例:
假设区间是[1, 3, 2, 5],此时left=0, right = 3,若i=3,则表示弄破3时,3的左右元素就是1(nums[left])和5(nums[right]),若i=2,则2被弄破时,2的左右元素是1(nums[left])和5(nums[right])。

结合上面的内容,整个完整的解释是:
对应区间[1, 3, 2, 5],如果i=3,表示着2在3之前被弄破,此时区间成了[1, 3, 5],而nums[left]=1,nums[right]=5就是3的左右元素,弄破3时的得分会是1×3×5=15。如果i=2,表示着3在2之前被弄破,此时区间成了[1, 2, 5],而nums[left]=1,nums[right]=5就是2的左右元素,弄破2时的得分会是1×2×5=10

下一个问题就是对于被处理后的nums,它的大小是n,它的区间[left, …, right]是怎样确定的呢?其实就是从n个元素中随意选择两个不相邻的元素。因为nums任意不相邻的两个元素都有可能成为某个i的相邻元素,只要区间内除了nums[left], nums[right]和i的元素都先于i被弄破。参考代码里的前两个循环完成的事情就是遍历了nums的所有区间。

然后我们用一个数组dp[n][n]存储所有区间能获得的最大得分。dp[left][right]表示弄破区间[nums[left], …, nums[right]]内的气球能得到的最大分数。而某一个区间弄破i的最大得分等于本次弄破i的得分+以i为界的子区间的最大得分,因为子区间内的气球会先于i被弄破。
即dp[left][right] = dp[left][i] + nums[left] * nums[i] * nums[right] + dp[i][right];
举个例子:[1, 3, 2, 5], left = 0, right = 3, i为索引,i=1
dp[0][3]
= dp[0][1] + nums[0] * nums[1] * nums[3] + dp[1][3]
= 0(因为区间只有两个元素,没有得分) + 1 * 3 * 5 + [3, 2, 5]的最大得分 (只有2先被弄破,1,5才能成为3的相邻元素)

由于我们需要求的是区间的最大得分,所以我们需要遍历区间的内的每一个i,并且取其中的最大值。这就是参考代码第三个循环完成的事情。

最后就是一个衔接的问题。之前讲过区间的边界元素会在区间内的元素都被弄破后才被弄破,比如上面的[1, 3, 2, 5]中5会是在3和2都被弄破后才被弄破,所以整个的问题就变成了给我们nums=[1, …, 1]这么一个区间,我们要求的是它的最大得分,而它的最大得分就是对于区间内所有i,取dp[left][i] + nums[i - 1] * nums[i] * nums[i + 1] + dp[i][right]的最大值,其中dp[left][i]和dp[i][right]的子区间最大值又是通过取孙子区间内所有j中的最大值得到的,依次类推下去。

由于我不是很会动态规划,所以没法给出类似子问题,状态转移方程这些动态规划概念的解释。只能说一下上述我个人的理解,如果没看懂,求别打脸。。。


代码

参考代码

class Solution {public:    int maxCoins(vector<int>& nums) {        nums.insert(nums.begin(), 1);        nums.push_back(1);        int n = nums.size();        vector<vector<int>> dp(n, vector<int>(n, 0));        for (int len = 2; len < n; len++) {            for (int left = 0; left < n - len; left++) {                int right = left + len;                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];    }};

总结

这道题仍待我好好思考一番,如果想懂了会再给出更准确的解答。
今天只能算是填了半个坑,不过不能过多浪费时间在同一题上,还有其他事情要忙,明天继续加油!

0 0