给你一个大小为 4 的整数数组 a 和一个大小 至少为 4 的整数数组 b。
你需要从数组 b 中选择四个下标 i0, i1, i2, 和 i3,并满足 i0 < i1 < i2 < i3。你的得分将是 a[0] * b[i0] + a[1] * b[i1] + a[2] * b[i2] + a[3] * b[i3] 的值。
返回你能够获得的 最大 得分。
示例 1:
输入: a = [3,2,5,6], b = [2,-6,4,-5,-3,2,-7]
输出: 26
解释:
选择下标 0, 1, 2 和 5。得分为 3 * 2 + 2 * (-6) + 5 * 4 + 6 * 2 = 26。
示例 2:
输入: a = [-1,4,5,-2], b = [-5,-1,-3,-2,-4]
输出: -1
解释:
选择下标 0, 1, 3 和 4。得分为 (-1) * (-5) + 4 * (-1) + 5 * (-2) + (-2) * (-4) = -1。
记忆化搜索
class Solution {
public:
long long maxScore(vector<int>& a, vector<int>& b) {
int n = b.size();
vector<array<long long, 4>> memo(n);
for(auto& row : memo){
ranges::fill(row, LLONG_MIN);
}
//j代表a[j]的索引,i代表b[i]的索引,只有在选择了目前状态后,才会有j-1或i-1。
auto dfs = [&](auto&& dfs, int i, int j) -> long long{
if(j < 0){
return 0;
}
if(i < 0){
return LLONG_MIN/2;
}
auto &res = memo[i][j];
if(res == LLONG_MIN){
res = max(dfs(dfs, i-1, j), dfs(dfs, i-1, j-1) + (long long)a[j] * b[i]);
}
return res;
};
return dfs(dfs, n-1, 3);
}
};
时间复杂度:O(mn),其中 m=4 是 a 的长度,n 是 b 的长度。由于每个状态只会计算一次,动态规划的时间复杂度 = 状态个数 × 单个状态的计算时间。本题状态个数等于 O(mn),单个状态的计算时间为 O(1),所以总的时间复杂度为 O(mn)。
空间复杂度:O(mn)。保存多少状态,就需要多少空间。
我们可以通过记忆化搜索,来让a数组从右往左的元素与b数组从右往左的元素一一匹配。
我们定义一个数组memo用于记忆化,储存递推的结果,避免重复运算。
我们在每次递推时候要进行判断,如果j小于0则a数组元素被选择完,返回0。如果a数组没被选择完的情况下,b数组被选择完,则返回一个非常小的负数LLONG_MIN/2,代表这种情况是不符合规范的,以这个为树子节点的路径是不成立的。
我们在定义递推的状态转换方程的时候,有两种情况,选择或者不选择b[i],如果选择了b[i],那么这时候a[j]会和b[i]相乘,a[j]和b[i]都被使用了,对应dfs(dfs, i-1, j-1) + (long long)a[j] * b[i]
。如果不选择b[i],那么就让a[j]和b[i-1]进行匹配,对应代码dfs(dfs, i-1, j)
。
我们最后返回答案dfs(dfs, n-1, 3);他会作为树的头节点,然后不断向下搜索,然后在a被选择完的时候停止搜索,我们在搜索的过程中选择能构成最大答案的路径就是答案。
1:1翻译成递推
class Solution {
public:
long long maxScore(vector<int>& a, vector<int>& b) {
int n = b.size();
vector<array<long long, 5>> f(n+1);
for(int j = 1; j < 5; j++){
f[0][j] = LLONG_MIN/2;
}
for(int i = 0; i < n; i++){
for(int j = 0; j < 4; j++){
f[i+1][j+1] = max(f[i][j+1], f[i][j] + (long long)a[j] * b[i]);
}
}
return f[n][4];
}
};
时间复杂度:O(mn),其中 m=4 是 a 的长度,n 是 b 的长度。
空间复杂度:O(mn)。
翻译成递推,我们从前往后,用a数组的左边开始元素与b数组的左边开始元素一一对应。
首先我们定义一个二维数组f[i][j],代表a数组的前j个元素和b数组的前i个元素中,最大的乘法组合是多少。然后我们初始化边界条件,由于当没有任何b元素可以组合的时候,对应代码f[0][j] = LLONG_MIN/2;
,代表这种状态是错误的。
空间优化
class Solution {
public:
long long maxScore(vector<int>& a, vector<int>& b) {
long long f[5]{};
fill(f+1, f+5,LLONG_MIN/2);
for(int y : b){
for(int j = 3; j >= 0; j--){
f[j+1] = max(f[j+1], f[j] + (long long)a[j] * y);
}
}
return f[4];
}
};
时间复杂度:O(mn),其中 m=4 是 a 的长度,n 是 b 的长度。
空间复杂度:O(m)。
我们可以看到在方法二中,f[i+1][j+1]是由上面一行同一列元素和上一行的前一列元素的状态转移而来,也就是由正上方和左上方元素状态转移而来。所以我们可以采用滚动数组的方式,从后往前进行滚动,也就是说在计算f的时候,正上方的元素和左上方元素在这一轮都没有被覆盖,保留这上一轮循环的值,所以我们可以只用一个一维数组f来储存答案。