Given two arrays nums1
and nums2
.
Return the maximum dot product between non-empty subsequences of nums1 and nums2 with the same length.
A subsequence of a array is a new array which is formed from the original array by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, [2,3,5]
is a subsequence of [1,2,3,4,5]
while [1,5,3]
is not).
Example 1:
Input: nums1 = [2,1,-2,5], nums2 = [3,0,-6] Output: 18 Explanation: Take subsequence [2,-2] from nums1 and subsequence [3,-6] from nums2. Their dot product is (2*3 + (-2)*(-6)) = 18.
Example 2:
Input: nums1 = [3,-2], nums2 = [2,-6,7] Output: 21 Explanation: Take subsequence [3] from nums1 and subsequence [7] from nums2. Their dot product is (3*7) = 21.
Example 3:
Input: nums1 = [-1,-1], nums2 = [1,1] Output: -1 Explanation: Take subsequence [-1] from nums1 and subsequence [1] from nums2. Their dot product is -1.
Constraints:
1 <= nums1.length, nums2.length <= 500
-1000 <= nums1[i], nums2[i] <= 1000
This problem asks to find the maximum dot product between non-empty subsequences of equal length from two input arrays, nums1
and nums2
. A brute-force approach would be computationally expensive. The optimal solution uses dynamic programming.
The dynamic programming approach builds a solution bottom-up. We create a 2D array, dp
, where dp[i][j]
represents the maximum dot product achievable using subsequences from the first i
elements of nums1
and the first j
elements of nums2
.
Base Case: dp[0][j] = dp[i][0] = -∞
(no subsequences possible with zero elements).
Recursive Relation: For dp[i][j]
, we consider two possibilities:
Exclude nums1[i-1]
or nums2[j-1]
: The maximum dot product is the maximum of dp[i-1][j]
and dp[i][j-1]
.
Include nums1[i-1]
and nums2[j-1]
: The dot product increases by nums1[i-1] * nums2[j-1]
. However, we need to consider if including this product makes the overall dot product negative. We take max(0, dp[i-1][j-1])
before adding the product, ensuring that a negative dot product from previous elements doesn't ruin the overall maximum.
Therefore, the recursive relation is:
dp[i][j] = max(dp[i-1][j], dp[i][j-1], max(0, dp[i-1][j-1]) + nums1[i-1] * nums2[j-1])
The final answer is dp[m][n]
, where m
and n
are the lengths of nums1
and nums2
, respectively.
Time Complexity: O(m*n), where 'm' and 'n' are the lengths of the input arrays. We iterate through the dp
array once.
Space Complexity: O(mn) to store the dp
array. We can optimize this to O(min(m,n)) using techniques like rolling arrays (only keeping the last row/column), but for simplicity, the provided solutions use O(mn) space.
The following code implements the dynamic programming solution in various programming languages. The logic remains the same across all implementations. Note the use of a large negative value (-inf
or Integer.MIN_VALUE
) to initialize the dp
array, representing an invalid or unreachable state.
class Solution:
def maxDotProduct(self, nums1: List[int], nums2: List[int]) -> int:
m, n = len(nums1), len(nums2)
dp = [[-float('inf')] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1], max(0, dp[i - 1][j - 1]) + nums1[i - 1] * nums2[j - 1])
return dp[m][n]
class Solution {
public int maxDotProduct(int[] nums1, int[] nums2) {
int m = nums1.length, n = nums2.length;
int[][] dp = new int[m + 1][n + 1];
for (int[] row : dp) Arrays.fill(row, Integer.MIN_VALUE);
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
dp[i][j] = Math.max(dp[i][j], Math.max(0, dp[i - 1][j - 1]) + nums1[i - 1] * nums2[j - 1]);
}
}
return dp[m][n];
}
}
class Solution {
public:
int maxDotProduct(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size(), n = nums2.size();
vector<vector<long long>> dp(m + 1, vector<long long>(n + 1, LLONG_MIN));
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
dp[i][j] = max(dp[i][j], max(0LL, dp[i - 1][j - 1]) + (long long)nums1[i - 1] * nums2[j - 1]);
}
}
return (int)dp[m][n];
}
};
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number}
*/
var maxDotProduct = function(nums1, nums2) {
const m = nums1.length;
const n = nums2.length;
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(-Infinity));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1], Math.max(0, dp[i - 1][j - 1]) + nums1[i - 1] * nums2[j - 1]);
}
}
return dp[m][n];
};
The other languages (Go, TypeScript, Rust) would follow a very similar pattern, implementing the same dynamic programming logic. The key is the initialization of the dp
array with a large negative value and the core recursive relation to build the solution efficiently.