You are given two arrays of integers, nums1
and nums2
. You need to select non-empty subsequences from both arrays (not necessarily contiguous, but order must be preserved), such that both subsequences are of the same length. Then, for each pair of corresponding elements in the chosen subsequences, multiply them together and sum the results. Your goal is to return the maximum possible dot product of any such pair of subsequences.
Constraints:
1 ≤ nums1.length, nums2.length ≤ 500
-1000 ≤ nums1[i], nums2[j] ≤ 1000
At first glance, the problem seems to suggest checking every possible subsequence pair, multiplying their elements, and finding the maximum sum. However, this would be extremely inefficient because the number of subsequences grows exponentially with array size.
To optimize, we notice that the problem is similar to finding the best alignment between two sequences, where each alignment is a pair of subsequences (with order preserved) and the "score" is their dot product. This is reminiscent of problems like "Longest Common Subsequence" but with multiplication and maximization.
The challenge is handling negative numbers, as sometimes skipping negative products is better, but in some cases, a negative times a negative yields a positive result. We must carefully decide at each step whether to pair the current elements or skip one (or both) to maximize the total dot product.
We use Dynamic Programming (DP) to efficiently compute the answer. Here's how:
dp[i][j]
represent the maximum dot product that can be achieved from the first i
elements of nums1
and the first j
elements of nums2
.(i, j)
, we have three options:
nums1[i]
with nums2[j]
(start or continue a subsequence): nums1[i] * nums2[j]
plus dp[i-1][j-1]
if we want to continue.nums1[i]
: consider dp[i-1][j]
.nums2[j]
: consider dp[i][j-1]
.dp[i][j]
.
dp[i-1][j-1]
is negative.dp[i][j] = max(nums1[i] * nums2[j], nums1[i] * nums2[j] + dp[i-1][j-1], dp[i-1][j], dp[i][j-1])
.dp
with very negative numbers (e.g., -inf
), so that we don't accidentally count empty subsequences.dp[m-1][n-1]
where m
and n
are the lengths of nums1
and nums2
.This DP approach ensures that for each pair of positions, we consider all possible ways to build up the best dot product, efficiently reusing previously computed results.
Input: nums1 = [2,1,-2,5]
, nums2 = [3,0,-6]
2
(from nums1
) with 3
(from nums2
): 2*3 = 6
5
with 3
: 5*3 = 15
5
with -6
: 5*(-6) = -30
-2
with -6
: -2*(-6) = 12
[2,5]
with [3,-6]
: 2*3 + 5*(-6) = 6 - 30 = -24
[1,5]
with [0,-6]
: 1*0 + 5*(-6) = 0 - 30 = -30
[2,-2,5]
with [3,0,-6]
: 2*3 + (-2)*0 + 5*(-6) = 6 + 0 - 30 = -24
[-2,5]
with [-6,3]
: -2*(-6) + 5*3 = 12 + 15 = 27
27
by pairing [-2,5]
with [-6,3]
.
nums1
and nums2
(exponential in size), check all alignments. This is O(2^m * 2^n)
and infeasible for large arrays.m x n
DP table, so time complexity is O(mn)
, where m
and n
are the lengths of nums1
and nums2
.O(mn)
for the DP table. This is efficient and feasible given the constraints.The Max Dot Product of Two Subsequences problem can be solved efficiently using dynamic programming. By carefully defining the state and transitions, we avoid redundant work and ensure that all valid subsequence pairings are considered. The key insight is to always consider the pairwise product at each step and use DP to track the best result so far, handling negative numbers and non-empty constraints gracefully. This approach is both elegant and practical for the problem's constraints.
from math import inf
class Solution:
def maxDotProduct(self, nums1, nums2):
m, n = len(nums1), len(nums2)
dp = [[-inf] * n for _ in range(m)]
for i in range(m):
for j in range(n):
prod = nums1[i] * nums2[j]
dp[i][j] = prod
if i > 0 and j > 0:
dp[i][j] = max(dp[i][j], prod + dp[i-1][j-1])
if i > 0:
dp[i][j] = max(dp[i][j], dp[i-1][j])
if j > 0:
dp[i][j] = max(dp[i][j], dp[i][j-1])
return dp[m-1][n-1]
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
class Solution {
public:
int maxDotProduct(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size(), n = nums2.size();
vector<vector<int>> dp(m, vector<int>(n, INT_MIN));
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
int prod = nums1[i] * nums2[j];
dp[i][j] = prod;
if (i > 0 && j > 0)
dp[i][j] = max(dp[i][j], prod + dp[i-1][j-1]);
if (i > 0)
dp[i][j] = max(dp[i][j], dp[i-1][j]);
if (j > 0)
dp[i][j] = max(dp[i][j], dp[i][j-1]);
}
}
return dp[m-1][n-1];
}
};
class Solution {
public int maxDotProduct(int[] nums1, int[] nums2) {
int m = nums1.length, n = nums2.length;
int[][] dp = new int[m][n];
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
int prod = nums1[i] * nums2[j];
dp[i][j] = prod;
if (i > 0 && j > 0)
dp[i][j] = Math.max(dp[i][j], prod + dp[i-1][j-1]);
if (i > 0)
dp[i][j] = Math.max(dp[i][j], dp[i-1][j]);
if (j > 0)
dp[i][j] = Math.max(dp[i][j], dp[i][j-1]);
}
}
return dp[m-1][n-1];
}
}
var maxDotProduct = function(nums1, nums2) {
const m = nums1.length, n = nums2.length;
const dp = Array.from({length: m}, () => Array(n).fill(Number.NEGATIVE_INFINITY));
for (let i = 0; i < m; ++i) {
for (let j = 0; j < n; ++j) {
let prod = nums1[i] * nums2[j];
dp[i][j] = prod;
if (i > 0 && j > 0)
dp[i][j] = Math.max(dp[i][j], prod + dp[i-1][j-1]);
if (i > 0)
dp[i][j] = Math.max(dp[i][j], dp[i-1][j]);
if (j > 0)
dp[i][j] = Math.max(dp[i][j], dp[i][j-1]);
}
}
return dp[m-1][n-1];
};