Get the Maximum Score
HardPrompt
You are given two sorted arrays of distinct integers nums1 and nums2.
A valid path is defined as follows:
- Choose array
nums1ornums2to traverse (from index-0). - Traverse the current array from left to right.
- If you are reading any value that is present in
nums1andnums2you are allowed to change your path to the other array. (Only one repeated value is considered in the valid path).
The score is defined as the sum of unique values in a valid path.
Return the maximum score you can obtain of all possible valid paths. Since the answer may be too large, return it modulo 109 + 7.
Example 1:
Input: nums1 = [2,4,5,8,10], nums2 = [4,6,8,9]
Output: 30
Explanation: Valid paths:
[2,4,5,8,10], [2,4,5,8,9], [2,4,6,8,9], [2,4,6,8,10], (starting from nums1)
[4,6,8,9], [4,5,8,10], [4,5,8,9], [4,6,8,10] (starting from nums2)
The maximum is obtained with the path in green [2,4,6,8,10].Example 2:
Input: nums1 = [1,3,5,7,9], nums2 = [3,5,100]
Output: 109
Explanation: Maximum sum is obtained with the path [1,3,5,100].Example 3:
Input: nums1 = [1,2,3,4,5], nums2 = [6,7,8,9,10]
Output: 40
Explanation: There are no common elements between nums1 and nums2.
Maximum sum is obtained with the path [6,7,8,9,10].
Constraints:
1 <= nums1.length, nums2.length <= 1051 <= nums1[i], nums2[i] <= 107nums1andnums2are strictly increasing.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach models the problem as finding the longest path from the start of either array. We can define a recursive function that calculates the maximum score from a given position in a given array. Since this recursive structure involves many overlapping subproblems (calculating the score from the same point multiple times), we can use memoization (a form of dynamic programming) to store the results of these subproblems and avoid redundant computations.
Algorithm
- We define a recursive function, let's call it
solve(index, isNums1), which calculates the maximum score starting fromindexin eithernums1(ifisNums1is true) ornums2. - To avoid recomputing results for the same state (e.g.,
solve(5, true)), we use two memoization arrays,memo1fornums1andmemo2fornums2. - Base Case: If the
indexgoes out of bounds for the respective array, the path ends, so we return 0. - Memoization Check: Before computing, we check if the result for the current state is already stored in our memoization table. If so, we return the stored value.
- Recursive Step for
solve(i, true)(i.e., fornums1[i]):- The score from this point will include
nums1[i]. The default next step is to continue innums1, so we recursively callsolve(i + 1, true). - We then check if
nums1[i]is a common element by searching for it innums2. A binary search is efficient for this sincenums2is sorted. - If
nums1[i]is found innums2at indexj, we have a choice: either continue innums1or switch tonums2. We calculate the score for switching, which would benums1[i] + solve(j + 1, false). - We take the maximum of the two options (staying vs. switching).
- Store the computed maximum score in
memo1[i]and return it.
- The score from this point will include
- The logic for
solve(j, false)(fornums2) is symmetrical. - The final answer is the maximum of starting from the beginning of
nums1ornums2, i.e.,max(solve(0, true), solve(0, false)). All additions should be done usinglongto prevent overflow, and the final result is taken modulo10^9 + 7.
Walkthrough
We can frame this problem recursively. The maximum score from any point i in an array is the value array[i] plus the maximum score achievable from the next steps. The next step can either be array[i+1] or, if array[i] is a common element, we can switch to the other array from the corresponding position.
This leads to overlapping subproblems. For instance, we might need to calculate the maximum score from nums1[k] multiple times through different paths. Dynamic programming with memoization is a perfect fit here.
We'll use two arrays, memo1 and memo2, to store the calculated maximum scores starting from each index of nums1 and nums2, respectively. The recursive function will first check the memoization table before performing any computation. To find if an element is common and get its index in the other array, we can use binary search, which is efficient because the arrays are sorted.
class Solution { long[] memo1; long[] memo2; int MOD = 1_000_000_007; int[] n1, n2; public int maxSum(int[] nums1, int[] nums2) { this.n1 = nums1; this.n2 = nums2; memo1 = new long[nums1.length]; memo2 = new long[nums2.length]; java.util.Arrays.fill(memo1, -1); java.util.Arrays.fill(memo2, -1); // We need to find the max score starting from index 0 of either array long maxScore = Math.max(solve(0, true), solve(0, false)); return (int) (maxScore % MOD); } private long solve(int index, boolean isNums1) { if (isNums1) { if (index >= n1.length) return 0; if (memo1[index] != -1) return memo1[index]; long currentVal = n1[index]; // Option 1: Continue in nums1 long res = currentVal + solve(index + 1, true); // Check for Option 2: Switch to nums2 if possible int otherIndex = java.util.Arrays.binarySearch(n2, (int)currentVal); if (otherIndex >= 0) { res = Math.max(res, currentVal + solve(otherIndex + 1, false)); } return memo1[index] = res; } else { if (index >= n2.length) return 0; if (memo2[index] != -1) return memo2[index]; long currentVal = n2[index]; // Option 1: Continue in nums2 long res = currentVal + solve(index + 1, false); // Check for Option 2: Switch to nums1 if possible int otherIndex = java.util.Arrays.binarySearch(n1, (int)currentVal); if (otherIndex >= 0) { res = Math.max(res, currentVal + solve(otherIndex + 1, true)); } return memo2[index] = res; } }}Complexity
Time
O(N * log(M) + M * log(N)). For each of the N states in `nums1`, we may perform a binary search on `nums2` (O(log M)). Similarly, for each of the M states in `nums2`, we may perform a binary search on `nums1` (O(log N)). Since each state is computed only once due to memoization, this is the total time complexity.
Space
O(N + M), where N and M are the lengths of `nums1` and `nums2`. This space is used for the memoization arrays `memo1` and `memo2`, and for the recursion stack.
Trade-offs
Pros
Conceptually straightforward as it directly translates the problem's recursive definition.
Guaranteed to find the correct optimal solution.
Cons
Significantly less efficient in terms of time complexity compared to the two-pointer approach.
The recursive implementation can lead to a
StackOverflowErrorfor very large inputs due to deep recursion. An iterative DP version would be safer but more complex to implement.Requires extra space for the memoization tables and the recursion stack.
This is a highly efficient greedy approach that leverages the fact that both input arrays are sorted. We can think of the paths as being composed of segments that lie between common elements (or from the start/to the end of the arrays). At each common element, we have a choice point. To maximize the total score, it's always optimal to choose the path segment that has a larger sum leading up to that common point. We can traverse both arrays simultaneously using two pointers to identify these segments and common points, making the greedy choice at each step.
Algorithm
- Initialize two pointers,
ifornums1andjfornums2, both at 0. - Initialize two
longvariables,sum1andsum2, to 0. These will store the sums of the current path segments in each array. - Initialize a
longvariableansto 0, which will accumulate the final maximum score. - Iterate with a
whileloop as long as both pointersiandjare within their respective array bounds.- Case 1:
nums1[i] < nums2[j]: The elementnums1[i]is not a common point. Add it to the current segment sum fornums1(sum1 += nums1[i]) and advance theipointer (i++). - Case 2:
nums2[j] < nums1[i]: Similarly, addnums2[j]tosum2and advance thejpointer. - Case 3:
nums1[i] == nums2[j]: A common element is found. This is a point where we can switch paths. To maximize the score, we choose the segment with the larger sum (Math.max(sum1, sum2)), add it to our total answerans, and also add the value of the common element itself. After this, we resetsum1andsum2to 0 for the next segments and advance both pointersiandj.
- Case 1:
- After the loop, one of the arrays might still have unvisited elements. These form the final segment of a possible path. We iterate through the remainder of
nums1(if any) adding tosum1, and the remainder ofnums2(if any) adding tosum2. - Finally, we add the larger of these two final segment sums (
Math.max(sum1, sum2)) to our total answerans. - Return
ansmodulo10^9 + 7.
Walkthrough
The core idea is to break down the problem into segments. A segment is a sequence of numbers in one array between two consecutive common elements. At every common element, we have the choice to switch from one array to the other. The greedy strategy is that to maximize the total sum, at each such junction, we should always come from the path that has accumulated a larger sum so far.
We can implement this efficiently using two pointers, one for each array. We advance the pointers, summing up elements for each array's current segment. When the pointers meet at a common value, we compare the segment sums, add the larger one to our total score, add the common value itself, and then reset the segment sums to start fresh for the next part of the journey.
class Solution { public int maxSum(int[] nums1, int[] nums2) { int n = nums1.length; int m = nums2.length; long sum1 = 0; long sum2 = 0; long ans = 0; int i = 0; int j = 0; int MOD = 1_000_000_007; while (i < n && j < m) { if (nums1[i] < nums2[j]) { sum1 += nums1[i]; i++; } else if (nums2[j] < nums1[i]) { sum2 += nums2[j]; j++; } else { // Common element found // Choose the path with the maximum sum so far ans += Math.max(sum1, sum2) + nums1[i]; // Reset sums for the next segment sum1 = 0; sum2 = 0; i++; j++; } } // Add remaining elements from nums1, if any while (i < n) { sum1 += nums1[i]; i++; } // Add remaining elements from nums2, if any while (j < m) { sum2 += nums2[j]; j++; } // Add the maximum of the last segment sums ans += Math.max(sum1, sum2); return (int) (ans % MOD); }}Complexity
Time
O(N + M), where N and M are the lengths of `nums1` and `nums2`. This is because each pointer, `i` and `j`, traverses its respective array at most once in a single pass.
Space
O(1). We only use a constant number of extra variables to store pointers and sums, regardless of the input size.
Trade-offs
Pros
Extremely efficient, providing an optimal linear time solution.
Constant space complexity, making it very memory-efficient.
The logic is clean and avoids the complexities of recursion and potential stack overflow issues.
Cons
The greedy logic might be slightly less intuitive to derive initially compared to a direct recursive approach.
Solutions
Solution
class Solution { public int maxSum ( int [] nums1 , int [] nums2 ) { final int mod = ( int ) 1 e9 + 7 ; int m = nums1 . length , n = nums2 . length ; int i = 0 , j = 0 ; long f = 0 , g = 0 ; while ( i < m || j < n ) { if ( i == m ) { g += nums2 [ j ++]; } else if ( j == n ) { f += nums1 [ i ++]; } else if ( nums1 [ i ] < nums2 [ j ]) { f += nums1 [ i ++]; } else if ( nums1 [ i ] > nums2 [ j ]) { g += nums2 [ j ++]; } else { f = g = Math . max ( f , g ) + nums1 [ i ]; i ++; j ++; } } return ( int ) ( Math . max ( f , g ) % mod ); } }class Solution { public: int maxSum ( vector < int >& nums1 , vector < int >& nums2 ) { const int mod = 1e9 + 7 ; int m = nums1 . size (), n = nums2 . size (); int i = 0 , j = 0 ; long long f = 0 , g = 0 ; while ( i < m || j < n ) { if ( i == m ) { g += nums2 [ j ++ ]; } else if ( j == n ) { f += nums1 [ i ++ ]; } else if ( nums1 [ i ] < nums2 [ j ]) { f += nums1 [ i ++ ]; } else if ( nums1 [ i ] > nums2 [ j ]) { g += nums2 [ j ++ ]; } else { f = g = max ( f , g ) + nums1 [ i ]; i ++ ; j ++ ; } } return max ( f , g ) % mod ; } };class Solution : def maxSum ( self , nums1 : List [ int ], nums2 : List [ int ]) -> int : mod = 10 ** 9 + 7 m , n = len ( nums1 ), len ( nums2 ) i = j = 0 f = g = 0 while i < m or j < n : if i == m : g += nums2 [ j ] j += 1 elif j == n : f += nums1 [ i ] i += 1 elif nums1 [ i ] < nums2 [ j ]: f += nums1 [ i ] i += 1 elif nums1 [ i ] > nums2 [ j ]: g += nums2 [ j ] j += 1 else : f = g = max ( f , g ) + nums1 [ i ] i += 1 j += 1 return max ( f , g ) % modVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.