[HOT 100] 1458. 两个子序列的最大点积

1. 题目链接


1458. 两个子序列的最大点积 - 力扣(LeetCode)


2. 题目描述


给你两个数组 nums1nums2

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

数组的非空子序列是通过删除原数组中某些元素(可能一个也不删除)后剩余数字组成的序列,但不能改变数字间相对顺序。比方说,[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. 解题思路


  1. 问题定义:我们需要找到两个数组st的非空子序列,使得它们的点积(对应元素相乘后求和)最大。
  2. 动态规划状态定义:使用memo[i][j]表示考虑数组s的前i个元素和数组t的前j个元素时的最大点积。
  3. 状态转移:
    • 可以选择将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]的值
  4. 边界条件:当ij小于0时,返回一个很小的值(这里用Integer.MIN_VALUE/2),表示无效状态。
  5. 记忆化搜索:使用记忆化技术避免重复计算。

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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值