1. 题目链接
1458. 两个子序列的最大点积 - 力扣(LeetCode)
2. 题目描述
给你两个数组 nums1
和 nums2
。
请你返回 nums1
和 nums2
中两个长度相同的 非空 子序列的最大点积。
数组的非空子序列是通过删除原数组中某些元素(可能一个也不删除)后剩余数字组成的序列,但不能改变数字间相对顺序。比方说,[2,3,5]
是 [1,2,3,4,5]
的一个子序列而 [1,5,3]
不是。
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 。
4. 解题思路
- 问题定义:我们需要找到两个数组
s
和t
的非空子序列,使得它们的点积(对应元素相乘后求和)最大。 - 动态规划状态定义:使用
memo[i][j]
表示考虑数组s
的前i
个元素和数组t
的前j
个元素时的最大点积。 - 状态转移:
- 可以选择将
s[i]
和t[j]
配对,此时最大点积为dfs(i-1,j-1) + s[i]*t[j]
- 也可以单独选择
s[i]*t[j]
作为一个新的起点 - 或者不考虑
s[i]
,即dfs(i-1,j)
- 或者不考虑
t[j]
,即dfs(i,j-1)
- 取这四种情况的最大值作为
memo[i][j]
的值
- 可以选择将
- 边界条件:当
i
或j
小于0时,返回一个很小的值(这里用Integer.MIN_VALUE/2
),表示无效状态。 - 记忆化搜索:使用记忆化技术避免重复计算。
5. 题解代码
import java.util.Arrays;
class Solution {
// 记忆化数组,memo[i][j]表示考虑s[0..i]和t[0..j]时的最大点积
private int[][] memo;
// 存储两个输入数组
private int[] s;
private int[] t;
public int maxDotProduct(int[] s, int[] t) {
this.s = s; // 保存数组s
this.t = t; // 保存数组t
int n = s.length, m = t.length; // 获取数组长度
memo = new int[n][m]; // 初始化记忆化数组
// 初始化记忆化数组为最小值,表示未计算状态
for (int[] row : memo) {
Arrays.fill(row, Integer.MIN_VALUE);
}
// 从最后一个元素开始递归计算
return dfs(n - 1, m - 1);
}
/**
* 递归计算s[0..i]和t[0..j]的最大点积
* @param i s数组的当前索引
* @param j t数组的当前索引
* @return 最大点积
*/
private int dfs(int i, int j) {
// 边界条件:当索引越界时返回一个很小的值(防止溢出)
if (i < 0 || j < 0) return Integer.MIN_VALUE / 2;
// 如果已经计算过,直接返回结果(记忆化)
if (memo[i][j] != Integer.MIN_VALUE) return memo[i][j];
// 计算当前元素的点积
int v = s[i] * t[j];
// 状态转移方程:
// 1. 将当前元素配对,加上之前的结果
// 2. 只取当前元素配对(作为新的起点)
// 3. 不考虑s[i]
// 4. 不考虑t[j]
memo[i][j] = Math.max(
Math.max(dfs(i - 1, j - 1) + v, v), // 情况1和2
Math.max(dfs(i - 1, j), dfs(i, j - 1)) // 情况3和4
);
return memo[i][j];
}
}
6. 复杂度分析
时间复杂度:O(n*m)
- 我们需要填充一个n×m的记忆化数组,每个状态的计算时间是O(1)
- 因此总时间复杂度为O(n*m)
空间复杂度:O(n*m)
- 主要空间消耗来自记忆化数组memo[n][m]
- 递归调用栈的深度最多为O(max(n,m)),相比memo数组可以忽略
- 因此总空间复杂度为O(n*m)