一、题目描述
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
二、题目分析
根据题意,我们需要在一个数组nums中选择其中一个数字(气球)burst,将它与它左右边的数字相乘(数组之外的数字视为1),并从数组中“移除”该数字,重复执行以上步骤直到数组为空。将每一步相乘的积累加,得到最终结果。我们的任务就是找出按一定顺序选择数字后能够得到的最大的累加和。
最简单的解法当然是枚举,进行全排列,但这样复杂度就达到了
O
(
n
!
)
O(n!)
O(n!) ,显然我们需要进一步的分析。如何让最后的和尽可能大呢?直接的一个想法是找出数组中最大的三个数字相乘,这里分别设它们为 nums[a]
,nusm[b]
,nums[c]
(a < b < c)
,为了达到这个目的,我们需要把它们两两之间的数字全部“移除”,已经“移除”的数字不会再影响后面的结果,并在最后将 nusm[b]
也会被burst。这里我们可以看到,实际上我们分别以 nums[a]
,nusm[b]
和nusm[b]
, nums[c]
为界限划分了两个组(不包括界限的数字),两个组的数字互不影响,也不会影响到界限数字,而为了达到最终目的,我们也同样需要这两个组的最终和最大——到这里我们似乎就发现了一个大的问题可以被分解为小的问题,但这显然还不够。
回到最开始,用上面分组的思想,我们可以将问题看成求解以数组之外两边的1为界限的组的最大和,然后就是在组中选择一个数字 nums[k]
,该数字会在最后与两边的1(即组的边界)相乘,然后我们又得到了两个小的组,然后在这两个小的组中求解。用 nums[first]
和 nums[last]
来表示组的边界,用 coinsRes[first][k]
和 coinsRes[k][last]
表示两个小的组的解,那么选择 nums[k]
作为分界的最好的结果就为
nums[first] * nums[k] * nums[last] + coinsRes[first][k] + coinsRes[k][last]
但我们不知道要选择哪个数字,也不知道小的组的解,所以最后我采用的是自底向上的动态规划的解法,从最小的那些组开始计算(即组距从小到大),将每一组的解保存在二维数组中(下标表示边界),这样最后就能得到最终解。
三、代码实现
class Solution {
public:
int maxCoins( vector<int> &nums ) {
int len = nums.size();
int **coinsRes = new int*[len + 2];
for ( int i = 0; i < len + 2; ++i )
coinsRes[i] = new int[len + 2](); // initialize to 0
// extendNums[] = { 1, nums, 1 }
int extendNums[len + 2] = {};
for ( int i = 1; i <= len; ++i ) {
extendNums[i] = nums.at( i-1 );
}
extendNums[0] = extendNums[len + 1] = 1;
for ( int i = 2; i <= len + 1; ++i ) { // The minimum group interval is 2
for ( int first = 0; first + i < len + 2; ++first ) {
int last = first + i;
// j represents boundary, diving the array[first, last] into two parts
for ( int j = first + 1; j < last; ++j ) {
coinsRes[first][last] = max( coinsRes[first][last],
extendNums[first] * extendNums[j] * extendNums[last] + coinsRes[first][j] + coinsRes[j][last] );
}
}
}
int res = coinsRes[0][len+1];
for ( int i = 0; i < len+2; ++i )
delete [] coinsRes[i];
delete [] coinsRes;
return res;
}
};