本题大概的题意是:给定一个数组,每次删除一个数字,在每次删除的同时得到它与其两边的数字的积的分数(两边没有数字则不乘),删除完之后原来两边的数字就变的相邻了。求可能的最高的分数。
一开始我想用的是贪心的做法:用链表存储整个数组,每次删除不再边界的最小的数。但是这样的方法毫无依据,果然WA了。
接着想到了用递归的方法,每次枚举最后被删除的数字。为了方便描述,设被删除的数字为a。每次枚举的时候用a乘以两端以外的值(例如,一开始两端意外的值为1和1),用它们的积加上分成两部分的用递归算出来的值(注意此时a已经成为了左边部分的右端以外的值,并且是右边部分的左边以外的值)。递归终点为该部分长度小于1.
递归的过程需要记录下左右端点的下标,和左右端点以外的值。
然而这样复杂度还是太高了,复杂度达到了O(n!)级别。经过具体提交发现当n大于等于20的时候就会超时。一开始我想能不能用记忆化来减小时间复杂度。但变量有四维,数组太大。于是我在想能否有办法能够剪枝,经过思考后无果。
后来我才发现,传递左右端点以外的值完全是没有必要的,我太拘泥于它们不在左右端点隔壁的情况。但只要仔细思考,它们在原来的数组中一定处于左右端点的隔壁。这样变量降到了二维,可以使用记忆化搜索。
具体实现需要重新构建一个左右端点为1的数组。代码如下:
# include <iostream>
# include <cstdio>
# include <cstring>
# include <vector>
using namespace std ;
# define INF 100000000
const int maxn = 550 ;
int d[maxn][maxn] ;
class Solution {
public:
int maxCoins(vector<int>& nums) {
vector<int> nums2 ;
nums2.push_back(1) ;
for (int i=0; i<nums.size(); i++) nums2.push_back(nums[i]) ;
nums2.push_back(1) ;
memset(d, -1, sizeof(d)) ;
return mydfs(nums2, 0, nums2.size()-1) ;
}
int mydfs(vector<int>& nums, int x, int y) {
if (x >= y - 1) return 0 ;
if (x == y - 2) return nums[x]*nums[x+1]*nums[y] ;
if (d[x][y] != -1) return d[x][y] ;
int ans = -INF ;
for (int i=x+1; i<y; i++) {
int curvalue = mydfs(nums, x, i) + mydfs(nums, i, y) + nums[x]*nums[i]*nums[y] ;
ans = max(ans, curvalue) ;
}
return d[x][y] = ans ;
}
};
其中使用到了记忆化搜索。
而用dp的方法也是可行的,递推公式如下:
当j < i+1 时 d[j][i] = max(d[j][i], d[j][k] + d[k][i] + nums[i] * nums[k] * nums[j] ) ; j < k < i
否则, d[j][i] = 0 ;
具体代码如下,注意j的枚举顺序:
# include <iostream>
# include <cstdio>
# include <cstring>
# include <vector>
using namespace std ;
# define INF 100000000
const int maxn = 550 ;
int d[maxn][maxn] ;
class Solution {
public:
int maxCoins(vector<int>& nums) {
vector<int> a ;
a.push_back(1) ;
for (int i=0; i<nums.size(); i++) a.push_back(nums[i]) ;
a.push_back(1) ;
int n = a.size() ;
memset(d, -1, sizeof(d)) ;
for (int i=0; i<n-1; i++) d[i][i+1] = 0 ;
for (int i=0; i<n; i++) d[i][i] = 0 ;
for (int i=0; i<n; i++) {
for (int j=i-1; j>=0; j--) {
for (int k=j+1; k<i; k++) {
d[j][i] = max(d[j][i], d[j][k] + d[k][i] + a[i]*a[k]*a[j]) ;
}
}
}
return d[0][n-1] ;
}
};
无论是记忆化搜索还是dp,算法的复杂度均为O(n3)。