入手的时候一直想打破表面,发现数学本质,结果并没有发现什么数学本质,直接去找答案。
挺复杂的,一般H的题如果我面试的时候遇到,用成语形容可能出现的情况,就是我可能会吃屎。这题对我来说是一定会吃屎。
dietpepsi给的答案很详细。
首先,一排气球左边是L,右边是R,我们可以假设最后一个打爆的气球是i,那倒数第二个打爆的气球j就出现在L-i或者i-R.
dp[L][R] = nums[i] * nums[L] * nums[R] + dp[L][i]+dp[i][R];
dp[L][R]表示从L打到R的最终得分。
nums[i] * nums[L] * nums[R]表示最后一次得分,这里我们已经假设L到i之间,i到R之间气球已经都打爆了。
刚才假设的内容,L到I之间,I到R之间都已经打爆了,但是我们没有加分,所以要加上dp[L][i]+dp[i][R],其实就是最后一个气球之前的气球。
DP[L][R]的时候要遍历,看看最后打哪个最合算,有很多重复计算,可以用DP记住。
dietpepsi的解释里面做了很多假设,看完之后我真射了。
题很难,如果不是碰到原题,面试遇到只能认栽。个人感觉更重要的是dietpepsi提供了很详细地思考过程,怎么一步一步思考找到答案,如何从n!到n^3。值得一看,可能比做出这一道题更重要:
(https://discuss.leetcode.com/topic/30746/share-some-analysis-and-explanations)
public class Solution
{
public int maxCoins(int[] oldnums)
{
int[] nums = new int[oldnums.length+2];
int index = 1;
nums[0] = 1;
for(int i: oldnums) nums[index++] = i;
nums[index] = 1;
int[][] dp = new int[nums.length][nums.length];
return helper(nums,0,nums.length-1,dp);
}
public int helper(int[] nums, int left, int right, int[][] dp)
{
if(dp[left][right]!=0) return dp[left][right];
int res = 0;
for(int i = left+1;i < right;i++)
{
res = Math.max(dp[left][right],
helper(nums,left,i,dp)+
helper(nums,i,right,dp)+
nums[i]*nums[left]*nums[right]);
dp[left][right] = res;
}
return res;
}
}
还有个更DP的做法,没理解,二刷再说。
二刷
二刷还是做晕了,今天状态好差,这几个H难度的题都只是理解做法了,但是并没有归纳总结出什么general的DP思想,DP对我来说千变万化。。。
这个题必须这么考虑。
Left score1 score2 score3 .... scoreN Right
假设有这么多气球,最边上2个left, right是1.
假设此时就剩1个气球,let's say its score3.
那么这最后一轮的得分是score3 * 他左边 * 他右边, 因为它是最后一个,他左右是Left和Right,都是1. 最后一轮得分,score3
倒数第二轮的情况就不一定了,他可能是是在(Left, score3)这个范围内,也可能在(left3, Right)这个范围内。
但是有一点肯定,不管是score3左边的区间,还是右边的区间,只要有气球被打爆,就加分。。 所以要加上递归那2个区间的结果。。
当前得分 + 左边区间得分 + 右边区间得分
在每次进入递归,然后算此区间打哪个气球最合算。
这里“打哪个气球”,是假设打的这个气球是此去见的最后一个,所以都要乘以当前区间的左右边界,而不是相邻的2个气球。
这里一开始搞乱了。。
public class Solution {
public int maxCoins(int[] nums) {
if (nums.length == 0) return 0;
int len = nums.length;
int[] scores = new int[len+2];
scores[0] = 1;
scores[len + 1] = 1;
for (int i = 0; i < len; i++) {
scores[i+1] = nums[i];
}
int[][] mem = new int[len+2][len+2];
return calScore(1, len, scores, mem);
}
public int calScore(int l, int r, int[] scores, int[][] mem) {
if (mem[l][r] != 0) return mem[l][r];
int tempMax = 0;
for (int i = l; i <= r; i++) {
int leftScore = calScore(l, i-1, scores, mem);
int rightScore = calScore(i+1, r, scores, mem);
int tempScore = scores[l-1] * scores[i] * scores[r+1];
tempMax = Math.max(tempMax, leftScore + rightScore + tempScore);
}
mem[l][r] = tempMax;
return tempMax;
}
}