LeetCode 312: Brust Balloon 解题与思考
题目描述
题目会输入一个长度为n的数列
an
(n从0开始算),你需要从中按照一定顺序取出所有的数字;每次取出一个数字
ak
时,你将得到该数字与其左右相邻数字的乘积的分数
ak−1∗ak∗ak+1
。
几点注意:
- 最左边的数的左边,以及最右边的数的右边视为1( a−1=an=1 )
- 取出之后这个数会从原数列消除,这个数的左右两个数将会变得相邻
题目要求求出对于给出的任意序列所能得到的得分最大值。
思路:
我最开始直观地感觉是这是一道动态规划的题目,因为假设我们取出某个数,这个问题就变成为:求剩下的数列中所能取得的最大值,是原问题的一个类似子问题
我们也可以得到一个关系式:
但是很快就遇到了如下问题:
- 取出一个数的得分和其左右两个数相关,而这两个数原则上可以是剩下的左边和右边的任意某个数(当然也能是1)
- 这个数被取走后,数列会被分成两半,而这两半独自的最大分数,与将这两个数列看做一个新数列所能取得的最大分数,并不存在关系:因为取走某个数之后,相邻关系会改变,左边的数可能会与右边的任意数相邻,反之亦然;之前求得的最大值毫无意义
但是动态规划的这个方向应该还是没问题的
所以接下来的工作重心,就在于
- 如何确定取走某个数之后所能得到的确切分数
- 如何建立原数列最大分数与新数列分数的关系
突破口在于,当数列中只有一个数,或者被取到只剩一个数(a[k])时,我们(理所当然地)知道最后我们能拿到这个数所得到的分数,为
或者说:
而且 取走这个数左边的数所能得到的最大分数,与取走这个数右边的数所能取得的最大分数相互无关,因为a[k]是最后取走的,此时左边的数列永远不会与右边的数相邻。
而此时,我们看左边的数列, 其最左边的数的左边是1,最右边的数的右边是a[k];而对于最右边的数列, 其最左边的数的左边是a[k],最右边的数的右边是1。倘若a[k] == 1,不难发现其和最初的数列类似,可以视作一个子问题处理。
算法
假设
ansp,q
代表数列中从第p项开始,包括该项在内的接下来q项的所构成新数列,所能取得的最大分数
ak
是该数列中最后取走的数,那么
其中
1、
p≤k<p+q
2、
ap−1
为数列最左边的数左边的数,
ap+q
为数列最右边的数右边的数,这两个数在操作这个数列的时候不会被取走,也不会有变化
3、
ansp,k−p
为
ap−1
与
ak
之间的数列所能取得的最大分数,
ansk+1,p+q−k−1
为
ak+1
与
ap+q
之间的数列所能取得的最大分数
4、
ansp,0=0
5、
ansp,1=ap−1∗ap∗ap+1
6、
ans0,n
为所求解
代码
#include <iostream>
#include <stdlib.h>
#include <vector>
#include <stdio.h>
using namespace std;
class Solution {
//取最大值函数
int max(int a, int b) {
return (a > b) ? a : b;
}
public:
int maxCoins(vector<int>& nums) {
int i, j, k;
int length = nums.size();
//新建数组并初始化为0
int *ans = (int*)(malloc(sizeof(int) * (length + 1) * (length + 1)));
memset((void*)ans, 0, sizeof(int) * (length + 1) * (length + 1));
//动态规划,三层循环
for ( i = 1; i < length + 1; i++ ) {
for ( j = 0; j < length - i + 1; j++ ) {
for ( k = j; k < j + i; k++ ) {
ans[j * length + i] = max(nums.at(k) * ((j <= 0) ? 1 : nums.at(j - 1)) * ((j + i >= length)?1:nums.at(j + i)) + ans[j * length + k - j] + ans[(k + 1) * length + j + i - k - 1], ans[j * length + i]);
}
}
}
//好习惯,申请的空间要释放
int result = ans[length];
free(ans);
//返回结果
return result;
}
};
思考
很有趣的一道题目,关键点在于从每次取走一个数,变成取走的是最后一个数,这一思维上的转变——而后者是能够轻松动态规划的