问题来源:leetcode 1458。
两个子序列的最大点积
给你两个数组 nums1
和 nums2
。
请你返回 nums1
和 nums2
中两个长度相同的 非空 子序列的最大点积。
数组的非空子序列是通过删除原数组中某些元素(可能一个也不删除)后剩余数字组成的序列,但不能改变数字间相对顺序。比方说,[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} Xn−1 和 Y m − 1 Y_{m-1} Ym−1 子序列的最大点积 d p [ n − 1 ] [ m − 1 ] dp[n-1][m-1] dp[n−1][m−1] 为 d p [ n ] [ m ] − x n ⋅ y m dp[n][m] - x_n\cdot y_m dp[n][m]−xn⋅ym,也就是说只需要求解子问题 d p [ n − 1 ] [ m − 1 ] dp[n-1][m-1] dp[n−1][m−1],可以通过反证法证明:假设不是最优解,那么存在一个更大的子序列的点积 d p ′ [ n − 1 ] [ m − 1 ] dp^{'}[n-1][m-1] dp′[n−1][m−1],在此基础上加上 x n ⋅ y m x_n \cdot y_m xn⋅ym,会得到一个比 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[n−1][m−1]+xn⋅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 和
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} Xn−1 与 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[n−1][m],也就是说只需要求解子问题 d p [ n − 1 ] [ m ] dp[n-1][m] dp[n−1][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} Ym−1 子序列的最大点积,即 d p [ n ] [ m ] = d p [ n ] [ m − 1 ] dp[n][m]=dp[n][m-1] dp[n][m]=dp[n][m−1],只需要求解子问题 d p [ n ] [ m − 1 ] dp[n][m-1] dp[n][m−1]。
- 如果 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} Xn−1 与 Y m − 1 Y_{m-1} Ym−1 子序列的最大点积,即 d p [ n ] [ m ] = d p [ n − 1 ] [ m − 1 ] dp[n][m]=dp[n-1][m-1] dp[n][m]=dp[n−1][m−1],只需要求解子问题 d p [ n − 1 ] [ m − 1 ] dp[n-1][m-1] dp[n−1][m−1]。
- 以上四种情况覆盖了求解 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{ x1⋅yk, k≤j };
- 当 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{ xk⋅y1, k≤i };
- 当
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[i−1][j−1], dp[i−1][j−1]+xiyj, dp[i−1][j], dp[i][j−1] }
自底向上地计算最优解的值:自左向右、自上而下地计算 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];
}
};