(DP動態規劃) 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:
- 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
題目解析與分析
題意
大概就是:有一排氣球,每個標了數字,然后戳一個氣球得到的獎勵是左氣球×該氣球×右氣球
,當然如果左或右沒有氣球就乘1。問該以什么順序戳氣球得到的獎勵最大
最笨的思路
枚舉法:將所有的情況列舉一遍。當有n
個氣球的時候,第一步我們有n
種選擇,第二步我們又有n-1
個選擇......顯然全枚舉的算法復雜度為,效率不敢恭維,因此這里就不實現——因為不可能過。
進一步想法
我們需要去考慮上面枚舉法做了什么重復的計算。我們可以想到,給定一組氣球,它所能獲得的最大的獎勵應該和前面已經被戳的氣球無關——被戳過的氣球只是在求和的時候累積上了而已。
對于給定k<n
, 其可能的組合數有 種,我們可以把
k
(從1開始)的所有情況都記錄在內存上,k+1
就可以基于k
進行計算,那么我們總共需要進行的計算就是
這種算法優于, 但仍然壞于
; 我們需要更優的算法
分治的想法
我們考慮用分治去思考這一道題。先是正常地考慮分治,我戳爆某個氣球,可不可以把剩下的氣球分成兩堆呢?這是否可行的前提是兩堆會不會互相干擾:答案是肯定的,在戳爆某個氣球以后,左堆的最右氣球會需要右堆的最左氣球來進行計算。
這時候我們需要反向的思維:我們正向地想戳氣球的過程,當然會導致兩堆相互影響。那如果我們考慮的是在這一堆里最后戳爆的那個氣球呢?假如A,B,C,D,E中我最后戳C, 那左堆(A,B)在戳的過程中顯然會以C為右邊界,而右堆(D,E)以C為左邊界——這就實現了分治,兩個子問題是相互不干擾的。
想一下為什么分治會更好,它少算了哪些步驟
具體的算法如下:
public int maxCoins(int[] iNums) {
int[] nums = new int[iNums.length + 2];
int n = 1;
for (int x : iNums) if (x > 0) nums[n++] = x;
nums[0] = nums[n++] = 1;
int[][] memo = new int[n][n];
return burst(memo, nums, 0, n - 1);
}
public int burst(int[][] memo, int[] nums, int left, int right) {
if (left + 1 == right) return 0;
if (memo[left][right] > 0) return memo[left][right];
int ans = 0;
for (int i = left + 1; i < right; ++i)
ans = Math.max(ans, nums[left] * nums[i] * nums[right]
+ burst(memo, nums, left, i) + burst(memo, nums, i, right));
memo[left][right] = ans;
return ans;
}
// 12 ms