leetcode--两个子序列的最大点积

 题目是LeetCode第190场周赛的第四题,链接: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 。

 这种又是子序列又是求最大值的不出意料的应该就是动态规划的题目了,既然知道是动态规划,剩下的问题就是怎么求递推公式了。因为是两个序列,所以不出意外应该是个二维动态规划,那么就定义dp[i][j]nums1[0:i]nums2[0:j]的最大点积,那最后的结果就是dp[m-1][n-1],其中mn分别为nums1nums2的长度。那么对于dp[i][j],要怎么通过之前的状态转移过来呢,可以根据nums1[i]nums2[j]是否加入最大点积序列分情况处理:

  • nums1[i]nums2[j]都加入最大点积序列,则dp[i][j]=max(nums1[i]*nums2[j], dp[i-1][j-1]+nums1[i]*nums2[j])(注意不要漏掉nums1[i]*nums2[j]这单独一项,因为dp[i-1][j-1]有可能小于0)
  • nums1[i]不加入最大点积序列,则dp[i][j]=dp[i-1][j]
  • nums2[j]不加入最大点积序列,则dp[i][j]=dp[i][j-1]
  • nums1[i]nums2[j]都不加入最大点积序列,则dp[i][j]=dp[i-1][j-1]

 时间复杂度为 O ( m n ) O(mn) O(mn),空间复杂度为 O ( m n ) O(mn) O(mn)

 JAVA版代码如下:

class Solution {
    public int maxDotProduct(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        int[][] dp = new int[m][n];
        // dp[i][j]: nums1前i与nums2前j之间的最大点积
        dp[0][0] = nums1[0] * nums2[0];
        int max = nums1[0];
        int min = nums1[0];
        for (int i = 1; i < m; ++i) {
            max = Math.max(nums1[i], max);
            min = Math.min(nums1[i], min);
            if (nums2[0] > 0) {
                dp[i][0] = max * nums2[0];
            }
            else {
                dp[i][0] = min * nums2[0];
            }
        }
        max = nums2[0];
        min = nums2[0];
        for (int j = 1; j < n; ++j) {
            max = Math.max(nums2[j], max);
            min = Math.min(nums2[j], min);
            if (nums1[0] > 0) {
                dp[0][j] = max * nums1[0];
            }
            else {
                dp[0][j] = min * nums1[0];
            }
        }
        for (int i = 1; i < m; ++i) {
            for (int j = 1; j < n; ++j) {
                dp[i][j] = Math.max(Math.max(nums1[i] * nums2[j], Math.max(dp[i - 1][j - 1], dp[i - 1][j - 1] + nums1[i] * nums2[j])), Math.max(dp[i - 1][j], dp[i][j - 1]));
            }
        }
        return dp[m - 1][n - 1];
    }
}

 提交结果如下:


 然后还是固定套路,通过观察状态转移方程,可以看到状态最远只依赖于i-1j-1,所以还是可以优化空间复杂度到 O ( m i n ( m , n ) ) O(min(m,n)) O(min(m,n)),时间复杂度还是 O ( m n ) O(mn) O(mn)

 JAVA版代码如下:

class Solution {
    public int maxDotProduct(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        if (m < n) {
            return maxDotProduct(nums2, nums1);
        }
        int[] dp = new int[n];
        // 第i次循环中的dp[j]: nums1前i与nums2前j之间的最大点积
        dp[0] = nums1[0] * nums2[0];
        int max = nums2[0];
        int min = nums2[0];
        for (int j = 1; j < n; ++j) {
            max = Math.max(nums2[j], max);
            min = Math.min(nums2[j], min);
            if (nums1[0] > 0) {
                dp[j] = max * nums1[0];
            }
            else {
                dp[j] = min * nums1[0];
            }
        }
        max = nums1[0];
        min = nums1[0];
        int dp_i_1_j_1;
        for (int i = 1; i < m; ++i) {
            max = Math.max(max, nums1[i]);
            min = Math.min(min, nums1[i]);
            dp_i_1_j_1 = dp[0];
            if (nums2[0] > 0) {
                dp[0] = max * nums2[0];
            }
            else {
                dp[0] = min * nums2[0];
            }
            for (int j = 1; j < n; ++j) {
                int tmp = dp[j];
                dp[j] = Math.max(Math.max(nums1[i] * nums2[j], Math.max(dp_i_1_j_1, dp_i_1_j_1 + nums1[i] * nums2[j])), Math.max(tmp, dp[j - 1]));
                dp_i_1_j_1 = tmp;
            }
        }
        return dp[n - 1];
    }
}

 提交结果如下:


 Python版代码如下:

class Solution:
    def maxDotProduct(self, nums1: List[int], nums2: List[int]) -> int:
        m = len(nums1)
        n = len(nums2)
        if m < n:
            return self.maxDotProduct(nums2, nums1)
        dp = [0] * n
        dp[0] = nums1[0] * nums2[0]
        maxN = minN = nums2[0]
        for j in range(1, n):
            maxN = max(maxN, nums2[j])
            minN = min(minN, nums2[j])
            dp[j] = maxN * nums1[0] if nums1[0] > 0 else minN * nums1[0]
        maxN = minN = nums1[0]
        for i in range(1, m):
            dp_i_1_j_1 = dp[0]
            maxN = max(maxN, nums1[i])
            minN = min(minN, nums1[i])
            dp[0] = maxN * nums2[0] if nums2[0] > 0 else minN * nums2[0]
            for j in range(1, n):
                tmp = dp[j]
                dp[j] = max(nums1[i] * nums2[j], dp_i_1_j_1 + nums1[i] * nums2[j],
                    dp_i_1_j_1, dp[j - 1], tmp)
                dp_i_1_j_1 = tmp
        return dp[n - 1]

 提交结果如下:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值