Create Maximum Number
HardPrompt
You are given two integer arrays nums1 and nums2 of lengths m and n respectively. nums1 and nums2 represent the digits of two numbers. You are also given an integer k.
Create the maximum number of length k <= m + n from digits of the two numbers. The relative order of the digits from the same array must be preserved.
Return an array of the k digits representing the answer.
Example 1:
Input: nums1 = [3,4,6,5], nums2 = [9,1,2,5,8,3], k = 5
Output: [9,8,6,5,3]Example 2:
Input: nums1 = [6,7], nums2 = [6,0,4], k = 5
Output: [6,7,6,0,4]Example 3:
Input: nums1 = [3,9], nums2 = [8,9], k = 3
Output: [9,8,9]
Constraints:
m == nums1.lengthn == nums2.length1 <= m, n <= 5000 <= nums1[i], nums2[i] <= 91 <= k <= m + nnums1andnums2do not have leading zeros.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach attempts to solve the problem by building the result number one digit at a time, from left to right, using recursion. A helper function is defined to find the maximum number of a certain length l from the remaining portions of the input arrays. To handle the choices for each digit, the function explores different possibilities. Since this leads to many overlapping subproblems, memoization is used to store and retrieve results for previously computed states, defined by the current positions in both arrays and the remaining length required.
Algorithm
- Define a recursive function, say
solve(i, j, l), which aims to find the maximum number of lengthlusing digits fromnums1starting at indexiandnums2starting at indexj. - To prevent recomputing results for the same state
(i, j, l), use a 3D array for memoization,memo[i][j][l]. - The base case for the recursion is when the required length
lis 0, in which case we return an empty sequence. - In each recursive call, determine the range of indices to search for the best possible first digit. We can discard a total of
(m-i) + (n-j) - ldigits from the start of the remaining arrays. This defines the search window. - Find the maximum digit
dwithin this valid search window in bothnums1andnums2. - If this maximum digit
dis found at multiple positions, we must recursively explore each choice. For each occurrence ofdatnums1[p], we form a candidate number by prependingdto the result ofsolve(p + 1, j, l - 1). Similarly for occurrences innums2. - Compare all candidate numbers generated from these recursive calls and select the lexicographically largest one.
- Store this result in the memoization table
memo[i][j][l]before returning.
Walkthrough
The core of this method is a recursive function, let's call it solve(i, j, l), which is designed to compute the maximum number of length l that can be formed from the subarrays nums1[i:] and nums2[j:].
To select the first digit of the result, we can't just pick any digit. We must ensure that enough digits remain in nums1 and nums2 to form the rest of the number (length l-1). The total number of available digits is (m-i) + (n-j). We can afford to skip (m-i) + (n-j) - l digits. This means we can search for our first digit in nums1[i ... i + d] and nums2[j ... j + d], where d is the number of skippable digits.
We greedily find the largest digit, max_d, in these search windows. A complication arises if max_d appears at multiple positions. For example, if nums1[p1] == max_d and nums2[q1] == max_d, we must recursively find out which choice leads to a better overall number. This means we have to compare the results of d + solve(p1 + 1, j, l - 1) and d + solve(i, q1 + 1, l - 1).
This recursive branching creates overlapping subproblems, as solve might be called with the same (i, j, l) arguments through different paths. A 3D DP table memo[i][j][l] is used to store the results. However, the size of this table, m * n * k, is prohibitively large for the given constraints (500 * 500 * 1000).
Complexity
Time
O(m * n * k * (m + n)). The state space is `m*n*k`. Each state computation involves a search over `m+n` elements. This is computationally infeasible.
Space
O(m * n * k) for the memoization table. Given m, n <= 500 and k <= 1000, this is too large.
Trade-offs
Pros
It represents a direct, albeit naive, recursive formulation of the problem.
Memoization helps avoid re-computation of the same subproblems.
Cons
Extremely high time complexity, making it too slow for the given constraints.
Very high space complexity due to the 3D memoization table, which is likely to exceed memory limits.
A more efficient approach is to use a divide and conquer strategy. The problem of forming a maximum number of length k is divided into subproblems: selecting i digits from nums1 and k-i digits from nums2. We iterate through all valid splits (i, k-i).
For each split, we solve two independent subproblems:
- Find the best possible subsequence of length
ifromnums1. - Find the best possible subsequence of length
k-ifromnums2.
Finding the best subsequence is a classic greedy problem that can be solved in linear time using a stack. After obtaining the two best subsequences, we need to merge them to form the final candidate number. This merge operation is also greedy: at each step, we pick the digit from the subsequence that is lexicographically larger from the current position. Finally, we compare the candidate from each split i and keep the overall best one.
Algorithm
- The main function iterates through all possible numbers of digits
ito take fromnums1. The number of digits fromnums2will then bek-i. The loop foriruns frommax(0, k - n)tomin(k, m). - For each
i, find the lexicographically largest subsequence of lengthifromnums1. This is done by a helper functionmaxSubsequence. - Similarly, find the largest subsequence of length
k-ifromnums2. - Merge the two resulting subsequences into a single candidate array of length
k. This is done by a helper functionmerge. - The
mergefunction compares the two subsequences element by element. If the current digits are different, it picks the larger one. If they are equal, it must look ahead to see which subsequence is lexicographically greater from the current position onwards. - Compare the merged candidate with the best result found so far. If the new candidate is larger, update the best result.
- After checking all possible values of
i, the best result found is the answer.
maxSubsequence(nums, k) Algorithm:
- Use a stack-like array
stackof sizekto build the result. - Iterate through
nums. For each digitd: a. While thestackis not empty, its top element is smaller thand, and we have enough remaining elements innumsto still form ak-length sequence after popping, pop from the stack. b. If thestackhas space, pushdonto it. - The
stackwill hold the maximum subsequence.
merge(sub1, sub2) Algorithm:
- Create a result array
mergedof sizek. - Use two pointers,
p1forsub1andp2forsub2. - In a loop that runs
ktimes, compare the remaining parts ofsub1(fromp1) andsub2(fromp2) using a helperisGreater. - Append the digit from the greater subsequence to
mergedand advance its pointer.
Walkthrough
This approach breaks the problem down into three main parts: iterating through all possible compositions, finding the maximum subsequence from a single array, and merging two subsequences.
-
Main Loop: We iterate through
i, the number of digits to take fromnums1. The valid range foriismax(0, k - n) <= i <= min(k, m). For eachi, we generate a candidate for the max number and compare it with our current best. -
maxSubsequence(nums, k): This helper finds the lexicographically largest subsequence of lengthk. It uses a greedy strategy with a stack. For each number in the input array, we decide whether to push it onto our result stack. We pop from the stack if the current number is larger than the top of the stack, and if popping doesn't prevent us from achieving ak-length result. This ensures our result is as large as possible from left to right. -
merge(sub1, sub2): This helper combines two subsequences into the largest possible number. It's similar to a standard merge, but when the leading digits of the remaining subsequences are equal, we must perform a lookahead comparison to decide which subsequence to take from. For example, merging[6, 7]and[6, 0, 4], we compare[6, 7]and[6, 0, 4]as a whole. Since7 > 0,[6, 7]is larger, so we take the6from it first.
Here is the Java implementation:
class Solution { public int[] maxNumber(int[] nums1, int[] nums2, int k) { int m = nums1.length; int n = nums2.length; int[] bestResult = new int[0]; for (int i = Math.max(0, k - n); i <= k && i <= m; i++) { int[] sub1 = maxSubsequence(nums1, i); int[] sub2 = maxSubsequence(nums2, k - i); int[] candidate = merge(sub1, sub2); if (isGreater(candidate, 0, bestResult, 0)) { bestResult = candidate; } } return bestResult; } private int[] maxSubsequence(int[] nums, int k) { int n = nums.length; int[] stack = new int[k]; int len = 0; for (int i = 0; i < n; i++) { while (len > 0 && stack[len - 1] < nums[i] && (n - i) > (k - len)) { len--; } if (len < k) { stack[len++] = nums[i]; } } return stack; } private int[] merge(int[] nums1, int[] nums2) { int k = nums1.length + nums2.length; int[] ans = new int[k]; int i = 0, j = 0, r = 0; while (r < k) { ans[r++] = isGreater(nums1, i, nums2, j) ? nums1[i++] : nums2[j++]; } return ans; } private boolean isGreater(int[] nums1, int p1, int[] nums2, int p2) { while (p1 < nums1.length && p2 < nums2.length && nums1[p1] == nums2[p2]) { p1++; p2++; } return p2 == nums2.length || (p1 < nums1.length && nums1[p1] > nums2[p2]); }}Complexity
Time
O(k * (m + n + k^2)) in a loose analysis. The loop runs `k` times. `maxSubsequence` is `O(m+n)`. `merge` can be `O(k^2)` in the worst case due to repeated lookaheads. However, a more careful analysis shows the total complexity is closer to `O(k * (m+n))` for many cases, and feasible for the given constraints.
Space
O(k). The space is used to store the subsequences and the candidate results, each of which has a length at most `k`.
Trade-offs
Pros
It is an efficient and correct algorithm that passes within the time limits for the given constraints.
The problem is neatly decomposed into smaller, reusable subproblems (
maxSubsequenceandmerge).
Cons
The logic, especially for the
mergefunction's lookahead comparison, can be complex to get right.The time complexity analysis is not immediately obvious.
Solutions
Solution
class Solution { public int [] maxNumber ( int [] nums1 , int [] nums2 , int k ) { int m = nums1 . length , n = nums2 . length ; int l = Math . max ( 0 , k - n ), r = Math . min ( k , m ); int [] ans = new int [ k ]; for ( int x = l ; x <= r ; ++ x ) { int [] arr1 = f ( nums1 , x ); int [] arr2 = f ( nums2 , k - x ); int [] arr = merge ( arr1 , arr2 ); if ( compare ( arr , ans , 0 , 0 )) { ans = arr ; } } return ans ; } private int [] f ( int [] nums , int k ) { int n = nums . length ; int [] stk = new int [ k ]; int top = - 1 ; int remain = n - k ; for ( int x : nums ) { while ( top >= 0 && stk [ top ] < x && remain > 0 ) { -- top ; -- remain ; } if ( top + 1 < k ) { stk [++ top ] = x ; } else { -- remain ; } } return stk ; } private int [] merge ( int [] nums1 , int [] nums2 ) { int m = nums1 . length , n = nums2 . length ; int i = 0 , j = 0 ; int [] ans = new int [ m + n ]; for ( int k = 0 ; k < m + n ; ++ k ) { if ( compare ( nums1 , nums2 , i , j )) { ans [ k ] = nums1 [ i ++]; } else { ans [ k ] = nums2 [ j ++]; } } return ans ; } private boolean compare ( int [] nums1 , int [] nums2 , int i , int j ) { if ( i >= nums1 . length ) { return false ; } if ( j >= nums2 . length ) { return true ; } if ( nums1 [ i ] > nums2 [ j ]) { return true ; } if ( nums1 [ i ] < nums2 [ j ]) { return false ; } return compare ( nums1 , nums2 , i + 1 , j + 1 ); } }class Solution { public: vector < int > maxNumber ( vector < int >& nums1 , vector < int >& nums2 , int k ) { auto f = []( vector < int >& nums , int k ) { int n = nums . size (); vector < int > stk ( k ); int top = - 1 ; int remain = n - k ; for ( int x : nums ) { while ( top >= 0 && stk [ top ] < x && remain > 0 ) { -- top ; -- remain ; } if ( top + 1 < k ) { stk [ ++ top ] = x ; } else { -- remain ; } } return stk ; }; function < bool ( vector < int >& , vector < int >& , int , int ) > compare = [ & ]( vector < int >& nums1 , vector < int >& nums2 , int i , int j ) -> bool { if ( i >= nums1 . size ()) { return false ; } if ( j >= nums2 . size ()) { return true ; } if ( nums1 [ i ] > nums2 [ j ]) { return true ; } if ( nums1 [ i ] < nums2 [ j ]) { return false ; } return compare ( nums1 , nums2 , i + 1 , j + 1 ); }; auto merge = [ & ]( vector < int >& nums1 , vector < int >& nums2 ) { int m = nums1 . size (), n = nums2 . size (); int i = 0 , j = 0 ; vector < int > ans ( m + n ); for ( int k = 0 ; k < m + n ; ++ k ) { if ( compare ( nums1 , nums2 , i , j )) { ans [ k ] = nums1 [ i ++ ]; } else { ans [ k ] = nums2 [ j ++ ]; } } return ans ; }; int m = nums1 . size (), n = nums2 . size (); int l = max ( 0 , k - n ), r = min ( k , m ); vector < int > ans ( k ); for ( int x = l ; x <= r ; ++ x ) { vector < int > arr1 = f ( nums1 , x ); vector < int > arr2 = f ( nums2 , k - x ); vector < int > arr = merge ( arr1 , arr2 ); if ( ans < arr ) { ans = move ( arr ); } } return ans ; } };class Solution : def maxNumber ( self , nums1 : List [ int ], nums2 : List [ int ], k : int ) -> List [ int ]: def f ( nums : List [ int ], k : int ) -> List [ int ]: n = len ( nums ) stk = [ 0 ] * k top = - 1 remain = n - k for x in nums : while top >= 0 and stk [ top ] < x and remain > 0 : top -= 1 remain -= 1 if top + 1 < k : top += 1 stk [ top ] = x else : remain -= 1 return stk def compare ( nums1 : List [ int ], nums2 : List [ int ], i : int , j : int ) -> bool : if i >= len ( nums1 ): return False if j >= len ( nums2 ): return True if nums1 [ i ] > nums2 [ j ]: return True if nums1 [ i ] < nums2 [ j ]: return False return compare ( nums1 , nums2 , i + 1 , j + 1 ) def merge ( nums1 : List [ int ], nums2 : List [ int ]) -> List [ int ]: m , n = len ( nums1 ), len ( nums2 ) i = j = 0 ans = [ 0 ] * ( m + n ) for k in range ( m + n ): if compare ( nums1 , nums2 , i , j ): ans [ k ] = nums1 [ i ] i += 1 else : ans [ k ] = nums2 [ j ] j += 1 return ans m , n = len ( nums1 ), len ( nums2 ) l , r = max ( 0 , k - n ), min ( k , m ) ans = [ 0 ] * k for x in range ( l , r + 1 ): arr1 = f ( nums1 , x ) arr2 = f ( nums2 , k - x ) arr = merge ( arr1 , arr2 ) if ans < arr : ans = arr return ansVideo 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.