【状态机DP】【记忆化搜索及翻译递推】【空间优化】力扣3290. 最高乘法得分

给你一个大小为 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来储存答案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值