动态规划——两个子序列的最大点积

问题来源:leetcode 1458

两个子序列的最大点积

给你两个数组 nums1nums2

请你返回 nums1nums2 中两个长度相同的 非空 子序列的最大点积。

数组的非空子序列是通过删除原数组中某些元素(可能一个也不删除)后剩余数字组成的序列,但不能改变数字间相对顺序。比方说,[2,3,5][1,2,3,4,5] 的一个子序列而 [1,5,3] 不是。

示例 1:

输入:nums1 = [2,1,-2,5], nums2 = [3,0,-6]
输出:18
解释:从 nums1 中得到子序列 [2,-2] ,从 nums2 中得到子序列 [3,-6] 。
它们的点积为 (2*3 + (-2)*(-6)) = 18 。

示例 2:

输入:nums1 = [3,-2], nums2 = [2,-6,7]
输出:21
解释:从 nums1 中得到子序列 [3] ,从 nums2 中得到子序列 [7] 。
它们的点积为 (3*7) = 21 。

示例 3:

输入:nums1 = [-1,-1], nums2 = [1,1]
输出:-1
解释:从 nums1 中得到子序列 [-1] ,从 nums2 中得到子序列 [1] 。
它们的点积为 -1 。

提示:

  • 1 <= nums1.length, nums2.length <= 500
  • -1000 <= nums1[i], nums2[i] <= 100

动态规划

优化子结构:设 x i x_i xi 表示数组 nums1 中的第 i i i 的元素, y j y_j yj 表示数组 nums2 中的第 j j j 个元素。证明原问题的最优解包含子问题的最优解,或者说原问题的最优解可以由子问题的最优解构造得到。设 X n = [ x 1 , . . . , x n ] X_n=[x_1,...,x_n] Xn=[x1,...,xn] Y m = [ y 1 , . . . , y m ] Y_m=[y_1,...,y_m] Ym=[y1,...,ym],设 X X X Y Y Y 子序列的最大点积为 d p [ n ] [ m ] dp[n][m] dp[n][m]

  • 如果 x n x_n xn y m y_m ym 的点积被包含在该最优解中,那么子问题 X n − 1 X_{n-1} Xn1 Y m − 1 Y_{m-1} Ym1 子序列的最大点积 d p [ n − 1 ] [ m − 1 ] dp[n-1][m-1] dp[n1][m1] d p [ n ] [ m ] − x n ⋅ y m dp[n][m] - x_n\cdot y_m dp[n][m]xnym,也就是说只需要求解子问题 d p [ n − 1 ] [ m − 1 ] dp[n-1][m-1] dp[n1][m1],可以通过反证法证明:假设不是最优解,那么存在一个更大的子序列的点积 d p ′ [ n − 1 ] [ m − 1 ] dp^{'}[n-1][m-1] dp[n1][m1],在此基础上加上 x n ⋅ y m x_n \cdot y_m xnym,会得到一个比 d p [ n ] [ m ] dp[n][m] dp[n][m] 更大的点积,与 d p [ n ] [ m ] dp[n][m] dp[n][m] X n X_n Xn Y m Y_m Ym 子序列的最大点积矛盾,所以在该情况下原问题的最优解包含其子问题的最优解,即 d p [ n ] [ m ] = d p [ n − 1 ] [ m − 1 ] + x n ⋅ y m dp[n][m]=dp[n-1][m-1]+x_n\cdot y_m dp[n][m]=dp[n1][m1]+xnym
  • 如果 x n x_n xn y m y_m ym 的点积不在最优解中,那么组成 X n X_n Xn Y m Y_m Ym 子序列的最大点积的子序列的结尾不可能同时为 x n x_n xn y m y_m ym
    • 如果 x n x_n xn 不参与最大点积的计算,那么 X n X_n Xn Y m Y_m Ym 子序列的最大点积可以是 X n − 1 X_{n-1} Xn1 Y m Y_m Ym 子序列的最大点积,即 d p [ n ] [ m ] = d p [ n − 1 ] [ m ] dp[n][m]=dp[n-1][m] dp[n][m]=dp[n1][m],也就是说只需要求解子问题 d p [ n − 1 ] [ m ] dp[n-1][m] dp[n1][m],反证法容易证明。
    • 如果 y m y_m ym 不参与最大点积的计算,那么 X n X_n Xn Y m Y_m Ym 子序列的最大点积可以是 X n X_{n} Xn Y m − 1 Y_{m-1} Ym1 子序列的最大点积,即 d p [ n ] [ m ] = d p [ n ] [ m − 1 ] dp[n][m]=dp[n][m-1] dp[n][m]=dp[n][m1]只需要求解子问题 d p [ n ] [ m − 1 ] dp[n][m-1] dp[n][m1]
    • 如果 x n x_n xn y m y_m ym 均不参与最大点积的计算,那么 X n X_n Xn Y m Y_m Ym 子序列的最大点积可以是 X n − 1 X_{n-1} Xn1 Y m − 1 Y_{m-1} Ym1 子序列的最大点积,即 d p [ n ] [ m ] = d p [ n − 1 ] [ m − 1 ] dp[n][m]=dp[n-1][m-1] dp[n][m]=dp[n1][m1]只需要求解子问题 d p [ n − 1 ] [ m − 1 ] dp[n-1][m-1] dp[n1][m1]
  • 以上四种情况覆盖了求解 X n X_n Xn Y m Y_m Ym 子序列最大点积的全部情况,从中选取使得 d p [ n ] [ m ] dp[n][m] dp[n][m] 最大的解的构造方式。

重叠子问题:举例说明计算 d p [ 2 ] [ 2 ] dp[2][2] dp[2][2] 时,需要计算子问题 d p [ 1 ] [ 2 ] dp[1][2] dp[1][2] d p [ 2 ] [ 1 ] dp[2][1] dp[2][1] d p [ 1 ] [ 1 ] dp[1][1] dp[1][1],而子问题 d p [ 1 ] [ 2 ] dp[1][2] dp[1][2] 计算时也需要计算 d p [ 1 ] [ 1 ] dp[1][1] dp[1][1],子问题 d p [ 1 ] [ 1 ] dp[1][1] dp[1][1] 被重复计算,所以存在重叠子问题。

递归地定义最优解的值:设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示数组 X i = [ x 1 , . . . , x i ] X_i=[x_1,...,x_i] Xi=[x1,...,xi] Y j = [ y 1 , . . . , y j ] Y_j=[y_1,...,y_j] Yj=[y1,...,yj] 的子序列的最长点积:

  • i = 1 i=1 i=1 时, d p [ 1 ] [ j ] dp[1][j] dp[1][j] m a x {   x 1 ⋅ y k ,   k ≤ j   } max\{\ x_1 \cdot y_k,\ k \leq j \ \} max{ x1yk, kj }
  • j = 1 j=1 j=1 时, d p [ i ] [ 1 ] dp[i][1] dp[i][1] m a x {   x k ⋅ y 1 ,   k ≤ i   } max\{\ x_k \cdot y_1,\ k \leq i \ \} max{ xky1, ki }
  • i > 1 i>1 i>1 j > 1 j>1 j>1 时:
    d p [ i ] [ j ] = m a x {   d p [ i − 1 ] [ j − 1 ] ,   d p [ i − 1 ] [ j − 1 ] + x i y j ,   d p [ i − 1 ] [ j ] ,   d p [ i ] [ j − 1 ]   } dp[i][j] = max\{\ dp[i-1][j-1],\ dp[i-1][j-1] + x_iy_j,\ dp[i-1][j],\ dp[i][j-1]\ \} dp[i][j]=max{ dp[i1][j1], dp[i1][j1]+xiyj, dp[i1][j], dp[i][j1] }

自底向上地计算最优解的值:自左向右、自上而下地计算 d p dp dp 数组的值,可以保证每个问题计算时相关的子问题均已经被计算过了。

class Solution {
public:
    int maxDotProduct(vector<int>& nums1, vector<int>& nums2) {
        // 二维数组
        int n = nums1.size(), m = nums2.size();
        vector<vector<int>> dp(n, vector<int>(m, 0));
        dp[0][0] = nums1[0] * nums2[0];
        for(int i=1; i<n; i++) {
            dp[i][0] = max(dp[i-1][0], nums1[i] * nums2[0]);
        }
        for(int j=1; j<m; j++) {
            dp[0][j] = max(dp[0][j-1], nums1[0] * nums2[j]);
        }
        for(int i=1; i<n; i++) {
            for(int j=1; j<m; j++) {
                dp[i][j] = max(max(dp[i][j-1], dp[i-1][j]), (dp[i-1][j-1] > 0 ? dp[i-1][j-1] : 0) + nums1[i] * nums2[j]);
            }
        }
        return dp[n-1][m-1];
    }
};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值