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 iyou 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:

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

Example:

Input: [3,1,5,8]
Output: 167
Explanation:
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

Credits:
Special thanks to @peisi for adding this problem and creating all test cases.

这道题提出了一种打气球的游戏,每个气球都对应着一个数字,每次打爆一个气球,得到的金币数是被打爆的气球的数字和其两边的气球上的数字相乘,如果旁边没有气球了,则按1算,以此类推,求能得到的最多金币数。参见题目中给的例子,题意并不难理解。那么大家拿到题后,总是会习惯的先去想一下暴力破解法吧,这道题的暴力搜索将相当的复杂,因为每打爆一个气球,断开的地方又重新挨上,所有剩下的气球又要重新遍历,这使得分治法不能 work,整个的时间复杂度会相当的高,不要指望可以通过 OJ。而对于像这种求极值问题,一般都要考虑用动态规划 Dynamic Programming 来做,维护一个二维动态数组 dp,其中 dp[i][j] 表示打爆区间 [i,j] 中的所有气球能得到的最多金币。题目中说明了边界情况,当气球周围没有气球的时候,旁边的数字按1算,这样可以在原数组两边各填充一个1,方便于计算。这道题的最难点就是找状态转移方程,还是从定义式来看,假如区间只有一个数,比如 dp[i][i],那么计算起来就很简单,直接乘以周围两个数字即可更新。如果区间里有两个数字,就要算两次了,先打破第一个再打破了第二个,或者先打破第二个再打破第一个,比较两种情况,其中较大值就是该区间的 dp 值。假如区间有三个数呢,比如 dp[1][3],怎么更新呢?如果先打破第一个,剩下两个怎么办呢,难道还要分别再遍历算一下吗?这样跟暴力搜索的方法有啥区别呢,还要 dp 数组有啥意思。所谓的状态转移,就是假设已知了其他状态,来推导现在的状态,现在是想知道 dp[1][3] 的值,那么如果先打破了气球1,剩下了气球2和3,若之前已经计算了 dp[2][3] 的话,就可以使用其来更新 dp[1][3] 了,就是打破气球1的得分加上 dp[2][3]。那假如先打破气球2呢,只要之前计算了 dp[1][1] 和 dp[3][3],那么三者加起来就可以更新 dp[1][3]。同理,先打破气球3,就用其得分加上 dp[1][2] 来更新 dp[1][3]。说到这里,是不是感觉豁然开朗了 ^.^

那么对于有很多数的区间 [i, j],如何来更新呢?现在是想知道 dp[i][j] 的值,这个区间可能比较大,但是如果知道了所有的小区间的 dp 值,然后聚沙成塔,逐步的就能推出大区间的 dp 值了。还是要遍历这个区间内的每个气球,就用k来遍历吧,k在区间 [i, j] 中,假如第k个气球最后被打爆,那么此时区间 [i, j] 被分成了三部分,[i, k-1],[k],和 [k+1, j],只要之前更新过了 [i, k-1] 和 [k+1, j] 这两个子区间的 dp 值,可以直接用 dp[i][k-1] 和 dp[k+1][j],那么最后被打爆的第k个气球的得分该怎么算呢,你可能会下意识的说,就乘以周围两个气球被 nums[k-1] * nums[k] * nums[k+1],但其实这样是错误的,为啥呢?dp[i][k-1] 的意义是什么呢,是打爆区间 [i, k-1] 内所有的气球后的最大得分,此时第 k-1 个气球已经不能用了,同理,第 k+1 个气球也不能用了,相当于区间 [i, j] 中除了第k个气球,其他的已经爆了,那么周围的气球只能是第 i-1 个,和第 j+1 个了,所以得分应为 nums[i-1] * nums[k] * nums[j+1],分析到这里,状态转移方程应该已经跃然纸上了吧,如下所示:

dp[i][j] = max(dp[i][j], nums[i - 1] * nums[k] * nums[j + 1] + dp[i][k - 1] + dp[k + 1][j])                 ( i ≤ k ≤ j )

有了状态转移方程了,就可以写代码,下面就遇到本题的第二大难点了,区间的遍历顺序。一般来说,遍历所有子区间的顺序都是i从0到n,然后j从i到n,然后得到的 [i, j] 就是子区间。但是这道题用这种遍历顺序就不对,在前面的分析中已经说了,这里需要先更新完所有的小区间,然后才能去更新大区间,而用这种一般的遍历子区间的顺序,会在更新完所有小区间之前就更新了大区间,从而不一定能算出正确的dp值,比如拿题目中的那个例子 [3, 1, 5, 8] 来说,一般的遍历顺序是:

[3] -> [3, 1] -> [3, 1, 5] -> [3, 1, 5, 8] -> [1] -> [1, 5] -> [1, 5, 8] -> [5] -> [5, 8] -> [8]

显然不是我们需要的遍历顺序,正确的顺序应该是先遍历完所有长度为1的区间,再是长度为2的区间,再依次累加长度,直到最后才遍历整个区间:

[3] -> [1] -> [5] -> [8] -> [3, 1] -> [1, 5] -> [5, 8] -> [3, 1, 5] -> [1, 5, 8] -> [3, 1, 5, 8]

这里其实只是更新了 dp 数组的右上三角区域,最终要返回的值存在 dp[1][n] 中,其中n是两端添加1之前数组 nums 的个数。参见代码如下:

解法一:

class Solution {
public:
int maxCoins(vector<int>& nums) {
int n = nums.size();
nums.insert(nums.begin(), );
nums.push_back();
vector<vector<int>> dp(n + , vector<int>(n + , ));
for (int len = ; len <= n; ++len) {
for (int i = ; i <= n - len + ; ++i) {
int j = i + len - ;
for (int k = i; k <= j; ++k) {
dp[i][j] = max(dp[i][j], nums[i - ] * nums[k] * nums[j + ] + dp[i][k - ] + dp[k + ][j]);
}
}
}
return dp[][n];
}
};

对于题目中的例子[3, 1, 5, 8],得到的dp数组如下:


这题还有递归解法,思路都一样,就是写法略有不同,参见代码如下:

解法二:

class Solution {
public:
int maxCoins(vector<int>& nums) {
int n = nums.size();
nums.insert(nums.begin(), );
nums.push_back();
vector<vector<int>> dp(n + , vector<int>(n + , ));
return burst(nums, dp, , n);
}
int burst(vector<int>& nums, vector<vector<int>>& dp, int i, int j) {
if (i > j) return ;
if (dp[i][j] > ) return dp[i][j];
int res = ;
for (int k = i; k <= j; ++k) {
res = max(res, nums[i - ] * nums[k] * nums[j + ] + burst(nums, dp, i, k - ) + burst(nums, dp, k + , j));
}
dp[i][j] = res;
return res;
}
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/312

类似题目:

Zuma Game

Remove Boxes

Strange Printer

参考资料:

https://leetcode.com/problems/burst-balloons/

https://leetcode.com/problems/burst-balloons/discuss/76228/Share-some-analysis-and-explanations

https://leetcode.com/problems/burst-balloons/discuss/76232/C%2B%2B-dynamic-programming-O(N3)-32-ms-with-comments

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] Burst Balloons 打气球游戏的更多相关文章

  1. [LeetCode] 312. Burst Balloons 打气球游戏

    Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by ...

  2. [LeetCode] Burst Balloons (Medium)

    Burst Balloons (Medium) 这题没有做出来. 自己的思路停留在暴力的解法, 时间复杂度很高: 初始化maxCount = 0. 对于当前长度为k的数组nums, 从0到k - 1逐 ...

  3. [LeetCode] 312. Burst Balloons 爆气球

    Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by ...

  4. LeetCode Burst Balloons

    原题链接在这里:https://leetcode.com/problems/burst-balloons/ 题目: Given n balloons, indexed from 0 to n-1. E ...

  5. 452. Minimum Number of Arrows to Burst Balloons扎气球的个数最少

    [抄题]: There are a number of spherical balloons spread in two-dimensional space. For each balloon, pr ...

  6. 312 Burst Balloons 戳气球

    现有 n 个气球按顺序排成一排,每个气球上标有一个数字,这些数字用数组 nums 表示.现在要求你戳破所有的气球.每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * ...

  7. LeetCode 312. Burst Balloons(戳气球)

    参考:LeetCode 312. Burst Balloons(戳气球) java代码如下 class Solution { //参考:https://blog.csdn.net/jmspan/art ...

  8. 【LeetCode】312. Burst Balloons 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址: https://leetcode.com/problems/burst-ba ...

  9. 【LeetCode】452. Minimum Number of Arrows to Burst Balloons 解题报告(Python)

    [LeetCode]452. Minimum Number of Arrows to Burst Balloons 解题报告(Python) 标签(空格分隔): LeetCode 题目地址:https ...

随机推荐

  1. ASP.NET Core 中文文档 第三章 原理(1)应用程序启动

    原文:Application Startup 作者:Steve Smith 翻译:刘怡(AlexLEWIS) 校对:谢炀(kiler398).许登洋(Seay) ASP.NET Core 为你的应用程 ...

  2. 简简单单学会C#位运算

    一.理解位运算 要学会位运算,首先要清楚什么是位运算?程序中的所有内容在计算机内存中都是以二进制的形式储存的(即:0或1),位运算就是直接对在内存中的二进制数的每位进行运算操作 二.理解数字进制 上面 ...

  3. Qt信号与槽自动关联机制

    参考链接1:http://blog.csdn.net/skyhawk452/article/details/6121407 参考链接2:http://blog.csdn.net/memory_exce ...

  4. Mac入门 (二) 使用VMware Fusion虚拟机

    有了Mac机,还是需在Mac上用Windows怎么办?, VMware Fusion 是运行在Mac机上的虚拟机软件, 类似于VMware workstation. 这样就可以在Mac上运行Windo ...

  5. Java Business Process Management(业务流程管理) 初识环境搭建

    一.简介 (一)什么是jbpm JBPM,全称是Java Business Process Management(业务流程管理),它是覆盖了业务流程管理.工作流.服务协作等领域的一个开源的.灵活的.易 ...

  6. 我的屌丝giser成长记-工作篇之A公司

    A公司是我研究生毕业的第一家GIS公司,一家专门做GIS应急的公司,接的项目还是可以的.A公司的项目框架GIS部分采取的是flexviwer,当然最近一两年来,flex技术在gis行业慢慢的被淘汰了, ...

  7. SharePoint 2013 日期和时间字段格式设置

    前言 最近碰到一个需求,用户希望修改日期和时间字段的格式,因为自己的环境是英文的,默认的时间格式是[月/日/年]这样的格式,我也是碰到这个问题才知道,这是美式的时间格式,然而用户希望变成英式的时间格式 ...

  8. iOS之在写一个iOS应用之前必须做的7件事(附相关资源)

    本文由CocoaChina--不再犹豫(tao200610704@126.com)翻译 作者:@NIkant Vohra 原文:7 Things you must absolutely do befo ...

  9. iOS多线程之7.NSOperation的初识

    NSOperation和GCD一样,不用我们管理线程的生命周期,加锁等问题,只要把操作封装进NSOperation中,系统会自动帮我们创建线程,执行操作.而且他是面向对象的,我们看起来更容易理解,使用 ...

  10. 嵌入式:J-link刷固件(坑)

    1.上电,短接ERASE,>10秒后,拔USB. 2.短接TST,上电,>10秒后,拔USB. 3.安装驱动.(看别人教程,下载到INF文件,WIN7不能右击安装,好,换虚拟机XP) 4. ...