Facebook Pixel

321. Create Maximum Number

Problem Description

You have two arrays of integers nums1 and nums2 with lengths m and n respectively. Each array represents the digits of a number. Your task is to create the maximum possible number of exactly k digits by selecting digits from both arrays.

The key constraints are:

  • The total length k must satisfy k <= m + n
  • When selecting digits from each array, you must maintain their original relative order within that array
  • You want to maximize the resulting number

For example, if nums1 = [3, 4, 6, 5] and nums2 = [9, 1, 2, 5, 8, 3], and k = 5, you need to pick a total of 5 digits from both arrays while preserving the relative order of digits from each array, such that the resulting 5-digit number is as large as possible.

The solution involves three main steps:

  1. Extract subsequences: For each possible split (taking x digits from nums1 and k-x digits from nums2), extract the maximum subsequence of the required length from each array using a monotonic stack approach.

  2. Merge subsequences: Combine the two subsequences into a single array while maintaining the maximum value at each position. This requires comparing the remaining elements lexicographically when the current elements are equal.

  3. Find the overall maximum: Try all valid combinations of how to split k between the two arrays and keep track of the maximum result.

The function f extracts the maximum subsequence of length k from a single array using a stack-based approach that maintains a decreasing monotonic stack while ensuring exactly k elements are selected.

The compare function performs lexicographic comparison between two arrays starting from given indices, which is crucial for the merge operation when current elements are equal.

The merge function combines two arrays into the maximum possible result by always choosing the larger element (or the one that leads to a larger result when elements are equal).

The main algorithm iterates through all valid ways to split k digits between the two arrays, generates the maximum subsequences for each split, merges them, and keeps track of the overall maximum.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The core insight is that we need to decompose this problem into smaller, manageable subproblems. Since we're picking digits from two arrays while preserving order, we can think of this as: "How many digits should I take from nums1 and how many from nums2?"

If we decide to take x digits from nums1, then we must take k - x digits from nums2. This gives us a finite number of possibilities to check. For each split, we need to solve two independent problems:

  1. What's the maximum number I can form by selecting x digits from nums1 while preserving order?
  2. What's the maximum number I can form by selecting k - x digits from nums2 while preserving order?

For extracting the maximum subsequence from a single array, we can use a greedy approach with a monotonic stack. The idea is: as we traverse the array, we want to keep larger digits towards the front. If we encounter a digit larger than what's currently in our result, we should consider removing smaller digits before it (as long as we have enough remaining digits to fill our required length). This is similar to removing digits to form the largest number - we maintain a decreasing sequence as much as possible.

Once we have the optimal subsequences from both arrays, merging them is like merging two sorted sequences, but with a twist. At each step, we want to pick the digit that will lead to the larger overall number. When the current digits are equal, we need to look ahead - we should pick from the array whose remaining portion is lexicographically larger.

The beauty of this approach is that it breaks down a complex problem into three simpler problems:

  1. Finding the maximum k-length subsequence from a single array (solved with monotonic stack)
  2. Merging two sequences optimally (solved with lookahead comparison)
  3. Trying all possible splits (solved with enumeration from max(0, k - n) to min(k, m))

The bounds for enumeration are important: we can't take more than m digits from nums1 or more than n digits from nums2, and we need exactly k total digits. This gives us the range [max(0, k - n), min(k, m)] for the number of digits to take from nums1.

Solution Approach

The solution implements three key functions that work together to solve the problem:

1. Extract Maximum Subsequence (f function)

This function extracts the maximum subsequence of length k from a single array using a monotonic stack approach:

def f(nums: List[int], k: int) -> List[int]:
    n = len(nums)
    stk = [0] * k  # Pre-allocate stack of size k
    top = -1       # Stack pointer
    remain = n - k # Number of elements we can skip

The algorithm maintains:

  • stk: A stack to build our result of exactly k digits
  • top: Current top position in the stack
  • remain: How many elements we can afford to skip (important for knowing when we can pop elements)

For each element x in nums:

  1. While the stack has elements, the top element is smaller than x, and we still have elements to spare (remain > 0), we pop from the stack
  2. If the stack isn't full (top + 1 < k), we push x onto the stack
  3. Otherwise, we skip x and decrement remain

This greedy approach ensures we get the lexicographically largest subsequence of length k.

2. Lexicographic Comparison (compare function)

This recursive function compares two arrays starting from indices i and j:

def compare(nums1: List[int], nums2: List[int], i: int, j: int) -> bool:

The logic:

  • If nums1 is exhausted (i >= len(nums1)), return False
  • If nums2 is exhausted (j >= len(nums2)), return True
  • If nums1[i] > nums2[j], nums1 is larger, return True
  • If nums1[i] < nums2[j], nums2 is larger, return False
  • If elements are equal, recursively compare the next elements

This lookahead comparison is crucial for the merge operation when current elements are equal.

3. Merge Two Subsequences (merge function)

This function merges two arrays to create the maximum possible result:

def merge(nums1: List[int], nums2: List[int]) -> List[int]:
    m, n = len(nums1), len(nums2)
    i = j = 0
    ans = [0] * (m + n)

At each position k in the result:

  • Use compare to determine which array to pick from
  • If compare(nums1, nums2, i, j) returns True, pick from nums1 and increment i
  • Otherwise, pick from nums2 and increment j

4. Main Algorithm

The main function ties everything together:

m, n = len(nums1), len(nums2)
l, r = max(0, k - n), min(k, m)  # Valid range for digits from nums1
ans = [0] * k

The bounds calculation:

  • Minimum from nums1: max(0, k - n) - we need at least k - n from nums1 if nums2 can't provide all k
  • Maximum from nums1: min(k, m) - we can't take more than m (size of nums1) or more than k total

For each valid split x (taking x from nums1 and k - x from nums2):

  1. Extract maximum subsequence of length x from nums1
  2. Extract maximum subsequence of length k - x from nums2
  3. Merge the two subsequences
  4. Update ans if the merged result is larger (Python lists compare lexicographically by default)

The time complexity is O(k * (m + n)) for each split, and we try at most k + 1 splits, giving us O(k^2 * (m + n)) overall.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's work through a concrete example to understand the solution approach.

Given:

  • nums1 = [3, 9]
  • nums2 = [8, 9]
  • k = 3

We need to select 3 total digits from both arrays while preserving their relative order to form the maximum possible number.

Step 1: Determine valid splits

We need to decide how many digits to take from each array. The valid range for digits from nums1 is:

  • Minimum: max(0, k - n) = max(0, 3 - 2) = 1 (we need at least 1 from nums1)
  • Maximum: min(k, m) = min(3, 2) = 2 (we can't take more than 2 from nums1)

So we'll try taking 1 or 2 digits from nums1.

Step 2: Try split 1 - Take 1 from nums1, 2 from nums2

Extract maximum subsequence of length 1 from nums1 = [3, 9]:

  • Start with empty stack, remain = 2 - 1 = 1
  • Process 3: Stack empty, push 3. Stack = [3]
  • Process 9: 9 > 3 and remain > 0, so pop 3 and push 9. Stack = [9]
  • Result: [9]

Extract maximum subsequence of length 2 from nums2 = [8, 9]:

  • Start with empty stack, remain = 2 - 2 = 0
  • Process 8: Stack empty, push 8. Stack = [8]
  • Process 9: 9 > 8 but remain = 0 (can't pop), push 9. Stack = [8, 9]
  • Result: [8, 9]

Merge [9] and [8, 9]:

  • Position 0: Compare [9] vs [8, 9]. Since 9 > 8, pick 9 from nums1
  • Position 1: Only nums2 remains, pick 8
  • Position 2: Only nums2 remains, pick 9
  • Merged result: [9, 8, 9]

Step 3: Try split 2 - Take 2 from nums1, 1 from nums2

Extract maximum subsequence of length 2 from nums1 = [3, 9]:

  • Start with empty stack, remain = 2 - 2 = 0
  • Process 3: Stack empty, push 3. Stack = [3]
  • Process 9: 9 > 3 but remain = 0 (can't pop), push 9. Stack = [3, 9]
  • Result: [3, 9]

Extract maximum subsequence of length 1 from nums2 = [8, 9]:

  • Start with empty stack, remain = 2 - 1 = 1
  • Process 8: Stack empty, push 8. Stack = [8]
  • Process 9: 9 > 8 and remain > 0, so pop 8 and push 9. Stack = [9]
  • Result: [9]

Merge [3, 9] and [9]:

  • Position 0: Compare [3, 9] vs [9]. Need to check nums1[0]=3 vs nums2[0]=9. Since 3 < 9, pick 9 from nums2
  • Position 1: Compare [3, 9] vs [] (nums2 exhausted). Pick 3 from nums1
  • Position 2: Only nums1 remains, pick 9
  • Merged result: [9, 3, 9]

Step 4: Compare all results

  • Split 1 gave us: [9, 8, 9]
  • Split 2 gave us: [9, 3, 9]

Comparing lexicographically: [9, 8, 9] > [9, 3, 9] (since 8 > 3 at position 1)

Final answer: [9, 8, 9]

This example demonstrates how the algorithm:

  1. Tries different ways to split k between the two arrays
  2. Uses a greedy stack-based approach to extract maximum subsequences
  3. Merges subsequences optimally using lookahead comparison
  4. Keeps track of the overall maximum across all splits

Solution Implementation

1class Solution:
2    def maxNumber(self, nums1: List[int], nums2: List[int], k: int) -> List[int]:
3        def get_max_subsequence(nums: List[int], k: int) -> List[int]:
4            """
5            Extract the maximum subsequence of length k from nums while maintaining order.
6            Uses a monotonic stack approach to greedily select largest elements.
7            """
8            n = len(nums)
9            stack = [0] * k  # Pre-allocate stack of size k
10            top = -1  # Stack pointer (top index)
11            elements_to_drop = n - k  # Number of elements we can skip
12          
13            for num in nums:
14                # Pop smaller elements from stack if we can still drop elements
15                while top >= 0 and stack[top] < num and elements_to_drop > 0:
16                    top -= 1
17                    elements_to_drop -= 1
18              
19                # Add current element if stack not full
20                if top + 1 < k:
21                    top += 1
22                    stack[top] = num
23                else:
24                    # Stack is full, just decrement elements_to_drop
25                    elements_to_drop -= 1
26          
27            return stack
28      
29        def is_greater(nums1: List[int], nums2: List[int], idx1: int, idx2: int) -> bool:
30            """
31            Compare two arrays starting from given indices.
32            Returns True if nums1[idx1:] is lexicographically greater than nums2[idx2:].
33            """
34            # If nums1 exhausted, it's not greater
35            if idx1 >= len(nums1):
36                return False
37            # If nums2 exhausted but nums1 has elements, nums1 is greater
38            if idx2 >= len(nums2):
39                return True
40            # Compare current elements
41            if nums1[idx1] > nums2[idx2]:
42                return True
43            if nums1[idx1] < nums2[idx2]:
44                return False
45            # If equal, recursively compare next elements
46            return is_greater(nums1, nums2, idx1 + 1, idx2 + 1)
47      
48        def merge_arrays(nums1: List[int], nums2: List[int]) -> List[int]:
49            """
50            Merge two arrays to create the maximum possible array.
51            Always picks the lexicographically larger remaining portion.
52            """
53            m, n = len(nums1), len(nums2)
54            idx1 = idx2 = 0
55            result = [0] * (m + n)
56          
57            for pos in range(m + n):
58                # Choose from nums1 if it has greater or equal remaining portion
59                if is_greater(nums1, nums2, idx1, idx2):
60                    result[pos] = nums1[idx1]
61                    idx1 += 1
62                else:
63                    result[pos] = nums2[idx2]
64                    idx2 += 1
65          
66            return result
67      
68        # Main logic
69        m, n = len(nums1), len(nums2)
70        # Determine valid range for elements to take from nums1
71        min_from_nums1 = max(0, k - n)  # Must take at least k-n from nums1
72        max_from_nums1 = min(k, m)      # Can take at most min(k, m) from nums1
73      
74        max_result = [0] * k
75      
76        # Try all valid splits between nums1 and nums2
77        for take_from_nums1 in range(min_from_nums1, max_from_nums1 + 1):
78            take_from_nums2 = k - take_from_nums1
79          
80            # Get maximum subsequences from each array
81            subsequence1 = get_max_subsequence(nums1, take_from_nums1)
82            subsequence2 = get_max_subsequence(nums2, take_from_nums2)
83          
84            # Merge the subsequences to get candidate result
85            candidate = merge_arrays(subsequence1, subsequence2)
86          
87            # Update max_result if candidate is lexicographically larger
88            if max_result < candidate:
89                max_result = candidate
90      
91        return max_result
92
1class Solution {
2    /**
3     * Create maximum number of length k from two arrays
4     * @param nums1 First input array
5     * @param nums2 Second input array
6     * @param k Target length of result array
7     * @return Maximum possible number of length k
8     */
9    public int[] maxNumber(int[] nums1, int[] nums2, int k) {
10        int m = nums1.length;
11        int n = nums2.length;
12      
13        // Calculate minimum and maximum possible elements to take from nums1
14        // We need at least (k - n) from nums1 if nums2 doesn't have enough elements
15        // We can take at most min(k, m) from nums1
16        int minFromNums1 = Math.max(0, k - n);
17        int maxFromNums1 = Math.min(k, m);
18      
19        int[] result = new int[k];
20      
21        // Try all possible combinations of taking x elements from nums1 and (k-x) from nums2
22        for (int countFromNums1 = minFromNums1; countFromNums1 <= maxFromNums1; ++countFromNums1) {
23            // Get maximum subsequence of length countFromNums1 from nums1
24            int[] maxSubseq1 = f(nums1, countFromNums1);
25            // Get maximum subsequence of length (k - countFromNums1) from nums2
26            int[] maxSubseq2 = f(nums2, k - countFromNums1);
27            // Merge the two subsequences to form maximum number
28            int[] mergedArray = merge(maxSubseq1, maxSubseq2);
29          
30            // Update result if current merged array is greater
31            if (compare(mergedArray, result, 0, 0)) {
32                result = mergedArray;
33            }
34        }
35      
36        return result;
37    }
38
39    /**
40     * Find maximum subsequence of length k from given array using monotonic stack
41     * @param nums Input array
42     * @param k Target subsequence length
43     * @return Maximum subsequence of length k
44     */
45    private int[] f(int[] nums, int k) {
46        int n = nums.length;
47        int[] stack = new int[k];
48        int top = -1;  // Stack pointer (index of top element)
49        int remainingToDiscard = n - k;  // Number of elements we can still discard
50      
51        for (int num : nums) {
52            // Pop smaller elements from stack if we can still discard elements
53            while (top >= 0 && stack[top] < num && remainingToDiscard > 0) {
54                --top;
55                --remainingToDiscard;
56            }
57          
58            // Push current element if stack is not full
59            if (top + 1 < k) {
60                stack[++top] = num;
61            } else {
62                // If stack is full, we discard current element
63                --remainingToDiscard;
64            }
65        }
66      
67        return stack;
68    }
69
70    /**
71     * Merge two arrays to create maximum possible number
72     * @param nums1 First array
73     * @param nums2 Second array
74     * @return Merged array forming maximum number
75     */
76    private int[] merge(int[] nums1, int[] nums2) {
77        int m = nums1.length;
78        int n = nums2.length;
79        int i = 0;  // Pointer for nums1
80        int j = 0;  // Pointer for nums2
81        int[] mergedResult = new int[m + n];
82      
83        // Merge arrays by always choosing the larger element
84        for (int k = 0; k < m + n; ++k) {
85            if (compare(nums1, nums2, i, j)) {
86                mergedResult[k] = nums1[i++];
87            } else {
88                mergedResult[k] = nums2[j++];
89            }
90        }
91      
92        return mergedResult;
93    }
94
95    /**
96     * Compare two arrays lexicographically starting from given indices
97     * @param nums1 First array
98     * @param nums2 Second array
99     * @param i Starting index in nums1
100     * @param j Starting index in nums2
101     * @return true if nums1[i:] >= nums2[j:], false otherwise
102     */
103    private boolean compare(int[] nums1, int[] nums2, int i, int j) {
104        // If nums1 is exhausted, nums2 is greater or equal
105        if (i >= nums1.length) {
106            return false;
107        }
108        // If nums2 is exhausted, nums1 is greater
109        if (j >= nums2.length) {
110            return true;
111        }
112        // Compare current elements
113        if (nums1[i] > nums2[j]) {
114            return true;
115        }
116        if (nums1[i] < nums2[j]) {
117            return false;
118        }
119        // If equal, recursively compare remaining elements
120        return compare(nums1, nums2, i + 1, j + 1);
121    }
122}
123
1class Solution {
2public:
3    vector<int> maxNumber(vector<int>& nums1, vector<int>& nums2, int k) {
4        // Lambda function to extract maximum subsequence of length k from a single array
5        // Uses monotonic stack approach to maintain the largest possible subsequence
6        auto extractMaxSubsequence = [](vector<int>& nums, int k) {
7            int n = nums.size();
8            vector<int> stack(k);  // Stack to store the result subsequence
9            int top = -1;          // Stack pointer (top index)
10            int remain = n - k;    // Number of elements we can afford to skip
11          
12            for (int num : nums) {
13                // Pop smaller elements from stack if we can still skip elements
14                while (top >= 0 && stack[top] < num && remain > 0) {
15                    --top;
16                    --remain;
17                }
18              
19                // Push current element if stack is not full
20                if (top + 1 < k) {
21                    stack[++top] = num;
22                } else {
23                    // Stack is full, skip current element
24                    --remain;
25                }
26            }
27            return stack;
28        };
29      
30        // Recursive lambda to compare two arrays lexicographically starting from given indices
31        // Returns true if nums1[i:] > nums2[j:], false otherwise
32        function<bool(vector<int>&, vector<int>&, int, int)> compareArrays = 
33            [&](vector<int>& nums1, vector<int>& nums2, int i, int j) -> bool {
34            // If nums1 is exhausted, nums1 is not greater
35            if (i >= nums1.size()) {
36                return false;
37            }
38            // If nums2 is exhausted, nums1 is greater
39            if (j >= nums2.size()) {
40                return true;
41            }
42            // Compare current elements
43            if (nums1[i] > nums2[j]) {
44                return true;
45            }
46            if (nums1[i] < nums2[j]) {
47                return false;
48            }
49            // Elements are equal, compare next positions
50            return compareArrays(nums1, nums2, i + 1, j + 1);
51        };
52      
53        // Lambda function to merge two arrays into maximum possible array
54        // Always picks the larger element at each step using compareArrays
55        auto mergeArrays = [&](vector<int>& nums1, vector<int>& nums2) {
56            int m = nums1.size();
57            int n = nums2.size();
58            int i = 0, j = 0;  // Pointers for nums1 and nums2
59            vector<int> result(m + n);
60          
61            // Merge by always choosing the element that leads to larger result
62            for (int idx = 0; idx < m + n; ++idx) {
63                if (compareArrays(nums1, nums2, i, j)) {
64                    result[idx] = nums1[i++];
65                } else {
66                    result[idx] = nums2[j++];
67                }
68            }
69            return result;
70        };
71      
72        // Main algorithm: try all possible splits of k between nums1 and nums2
73        int m = nums1.size();
74        int n = nums2.size();
75        int minFromNums1 = max(0, k - n);  // Minimum elements needed from nums1
76        int maxFromNums1 = min(k, m);      // Maximum elements we can take from nums1
77      
78        vector<int> result(k);  // Initialize result with zeros
79      
80        // Try all valid splits
81        for (int countFromNums1 = minFromNums1; countFromNums1 <= maxFromNums1; ++countFromNums1) {
82            int countFromNums2 = k - countFromNums1;
83          
84            // Extract maximum subsequences from both arrays
85            vector<int> subsequence1 = extractMaxSubsequence(nums1, countFromNums1);
86            vector<int> subsequence2 = extractMaxSubsequence(nums2, countFromNums2);
87          
88            // Merge the subsequences to get candidate result
89            vector<int> candidate = mergeArrays(subsequence1, subsequence2);
90          
91            // Update result if candidate is lexicographically larger
92            if (result < candidate) {
93                result = move(candidate);
94            }
95        }
96      
97        return result;
98    }
99};
100
1/**
2 * Creates maximum number of length k from two arrays
3 * @param nums1 - First input array
4 * @param nums2 - Second input array
5 * @param k - Length of the result array
6 * @returns Maximum number array of length k
7 */
8function maxNumber(nums1: number[], nums2: number[], k: number): number[] {
9    const firstArrayLength: number = nums1.length;
10    const secondArrayLength: number = nums2.length;
11  
12    // Calculate the minimum and maximum number of digits we can take from nums1
13    const minDigitsFromFirst: number = Math.max(0, k - secondArrayLength);
14    const maxDigitsFromFirst: number = Math.min(k, firstArrayLength);
15  
16    // Initialize result array with zeros
17    let result: number[] = Array(k).fill(0);
18  
19    // Try all possible combinations of taking x digits from nums1 and (k-x) from nums2
20    for (let digitsFromFirst = minDigitsFromFirst; digitsFromFirst <= maxDigitsFromFirst; ++digitsFromFirst) {
21        // Get maximum subsequence of length x from nums1
22        const subsequence1: number[] = f(nums1, digitsFromFirst);
23        // Get maximum subsequence of length (k-x) from nums2
24        const subsequence2: number[] = f(nums2, k - digitsFromFirst);
25        // Merge the two subsequences to get maximum possible number
26        const mergedArray: number[] = merge(subsequence1, subsequence2);
27      
28        // Update result if current combination is greater
29        if (compare(mergedArray, result, 0, 0)) {
30            result = mergedArray;
31        }
32    }
33  
34    return result;
35}
36
37/**
38 * Extracts maximum subsequence of length k from array while maintaining order
39 * Uses monotonic stack approach
40 * @param nums - Input array
41 * @param k - Required subsequence length
42 * @returns Maximum subsequence of length k
43 */
44function f(nums: number[], k: number): number[] {
45    const arrayLength: number = nums.length;
46    // Stack to store the result subsequence
47    const stack: number[] = Array(k).fill(0);
48    let stackTop: number = -1;
49    // Number of elements we can still drop
50    let remainingToDrop: number = arrayLength - k;
51  
52    for (const currentNum of nums) {
53        // Pop smaller elements from stack if we can still drop elements
54        while (stackTop >= 0 && stack[stackTop] < currentNum && remainingToDrop > 0) {
55            --stackTop;
56            --remainingToDrop;
57        }
58      
59        // Add current element if stack is not full
60        if (stackTop + 1 < k) {
61            stack[++stackTop] = currentNum;
62        } else {
63            // Skip current element if stack is full
64            --remainingToDrop;
65        }
66    }
67  
68    return stack;
69}
70
71/**
72 * Compares two arrays lexicographically starting from given indices
73 * @param nums1 - First array to compare
74 * @param nums2 - Second array to compare
75 * @param i - Starting index for nums1
76 * @param j - Starting index for nums2
77 * @returns True if nums1[i:] > nums2[j:], false otherwise
78 */
79function compare(nums1: number[], nums2: number[], i: number, j: number): boolean {
80    // nums1 exhausted, so nums2 is greater or equal
81    if (i >= nums1.length) {
82        return false;
83    }
84    // nums2 exhausted, so nums1 is greater
85    if (j >= nums2.length) {
86        return true;
87    }
88    // Compare current elements
89    if (nums1[i] > nums2[j]) {
90        return true;
91    }
92    if (nums1[i] < nums2[j]) {
93        return false;
94    }
95    // Elements are equal, compare next positions
96    return compare(nums1, nums2, i + 1, j + 1);
97}
98
99/**
100 * Merges two arrays to create maximum number while maintaining relative order
101 * @param nums1 - First array to merge
102 * @param nums2 - Second array to merge
103 * @returns Merged array forming maximum number
104 */
105function merge(nums1: number[], nums2: number[]): number[] {
106    const firstLength: number = nums1.length;
107    const secondLength: number = nums2.length;
108    const mergedResult: number[] = Array(firstLength + secondLength).fill(0);
109    let firstIndex: number = 0;
110    let secondIndex: number = 0;
111  
112    // Merge arrays by always choosing the lexicographically larger remaining part
113    for (let mergedIndex = 0; mergedIndex < firstLength + secondLength; ++mergedIndex) {
114        if (compare(nums1, nums2, firstIndex, secondIndex)) {
115            mergedResult[mergedIndex] = nums1[firstIndex++];
116        } else {
117            mergedResult[mergedIndex] = nums2[secondIndex++];
118        }
119    }
120  
121    return mergedResult;
122}
123

Time and Space Complexity

Time Complexity: O((m + n)³)

The analysis breaks down as follows:

  1. The main loop iterates from l to r, which is at most min(k, m) iterations, so O(k) iterations.

  2. For each iteration:

    • Function f(nums1, x): Takes O(m) time to extract subsequence of length x from nums1
    • Function f(nums2, k - x): Takes O(n) time to extract subsequence of length k - x from nums2
    • Function merge(arr1, arr2):
      • Creates an array of size k and fills it
      • For each position, calls compare() which in worst case compares all remaining elements
      • Worst case for compare() is O(k) when arrays have many equal prefixes
      • Total merge time: O(k²)
    • Array comparison ans < arr: Takes O(k) time
  3. Overall time complexity:

    • Number of iterations: O(k)
    • Work per iteration: O(m) + O(n) + O(k²) + O(k)
    • Since k ≤ m + n, we have O(k) ≤ O(m + n)
    • Total: O(k) × O(k²) = O(k³) = O((m + n)³) in the worst case

Space Complexity: O(m + n)

The space analysis:

  1. Function f(): Creates a stack of size k, so O(k) space
  2. Function compare(): Uses recursion with maximum depth O(min(len(nums1), len(nums2))), which is O(m + n) in worst case
  3. Function merge(): Creates an array of size m + n for merging, so O(m + n) space
  4. Main function: Stores arr1, arr2, arr, and ans, each of size at most k, so O(k) space
  5. Since k ≤ m + n, the dominant space complexity is O(m + n)

Common Pitfalls

1. Incorrect Merge Logic When Elements Are Equal

Pitfall: A naive merge implementation might simply compare current elements and pick the larger one, without considering what comes after when elements are equal.

# INCORRECT merge approach
def merge_wrong(nums1, nums2):
    result = []
    i = j = 0
    while i < len(nums1) and j < len(nums2):
        if nums1[i] >= nums2[j]:  # Wrong! Doesn't consider future elements
            result.append(nums1[i])
            i += 1
        else:
            result.append(nums2[j])
            j += 1
    # ... append remaining elements

Why it fails: Consider nums1 = [6, 7] and nums2 = [6, 0, 4]. The naive approach would pick 6 from nums1 first (since 6 >= 6), resulting in [6, 6, 0, 4, 7]. However, the optimal merge is [6, 6, 7, 0, 4] by picking from nums2 first.

Solution: Use lexicographic comparison of the remaining portions:

def is_greater(nums1, nums2, i, j):
    while i < len(nums1) and j < len(nums2):
        if nums1[i] != nums2[j]:
            return nums1[i] > nums2[j]
        i += 1
        j += 1
    return i < len(nums1)  # nums1 has more elements remaining

2. Off-by-One Errors in Stack Implementation

Pitfall: Incorrectly managing the stack pointer or the count of elements that can be dropped.

# INCORRECT stack implementation
def get_max_wrong(nums, k):
    stack = []
    drop_count = len(nums) - k
    for num in nums:
        while stack and stack[-1] < num and drop_count > 0:
            stack.pop()
            drop_count -= 1
        stack.append(num)  # Wrong! May exceed k elements
    return stack[:k]  # Trimming afterwards is inefficient and error-prone

Why it fails: This approach might add more than k elements to the stack and relies on trimming, which can lead to suboptimal results if not carefully handled.

Solution: Maintain exact control over stack size:

def get_max_correct(nums, k):
    n = len(nums)
    stack = [0] * k
    top = -1
    remain = n - k
  
    for num in nums:
        while top >= 0 and stack[top] < num and remain > 0:
            top -= 1
            remain -= 1
        if top + 1 < k:
            top += 1
            stack[top] = num
        else:
            remain -= 1
    return stack

3. Incorrect Bounds Calculation for Split Range

Pitfall: Not correctly calculating the valid range of how many elements to take from each array.

# INCORRECT bounds
for take_from_nums1 in range(k + 1):  # Wrong! Doesn't check array sizes
    take_from_nums2 = k - take_from_nums1
    # This might try to take more elements than available

Why it fails: If k = 5, len(nums1) = 3, len(nums2) = 2, trying to take 4 from nums1 is impossible.

Solution: Calculate proper bounds:

min_from_nums1 = max(0, k - len(nums2))  # Must take at least this many
max_from_nums1 = min(k, len(nums1))      # Can take at most this many

for take_from_nums1 in range(min_from_nums1, max_from_nums1 + 1):
    take_from_nums2 = k - take_from_nums1
    # Now guaranteed: 0 <= take_from_nums1 <= len(nums1)
    #                 0 <= take_from_nums2 <= len(nums2)

4. Stack Overflow in Recursive Comparison

Pitfall: The recursive is_greater function can cause stack overflow for very long arrays.

Solution: Use an iterative approach:

def is_greater_iterative(nums1, nums2, i, j):
    while i < len(nums1) and j < len(nums2):
        if nums1[i] != nums2[j]:
            return nums1[i] > nums2[j]
        i += 1
        j += 1
    return i < len(nums1)

5. Not Handling Edge Cases

Pitfall: Failing to handle cases where one array is empty or k equals the total length.

Solution: The bounds calculation naturally handles these cases, but always verify:

  • Empty array: The algorithm should work with nums1 = [] or nums2 = []
  • k = m + n: Should return all elements merged optimally
  • k = 1: Should return the single largest element from either array
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

In a binary min heap, the maximum element can be found in:


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More