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 satisfyk <= 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:
-
Extract subsequences: For each possible split (taking
x
digits fromnums1
andk-x
digits fromnums2
), extract the maximum subsequence of the required length from each array using a monotonic stack approach. -
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.
-
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.
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:
- What's the maximum number I can form by selecting
x
digits fromnums1
while preserving order? - What's the maximum number I can form by selecting
k - x
digits fromnums2
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:
- Finding the maximum k-length subsequence from a single array (solved with monotonic stack)
- Merging two sequences optimally (solved with lookahead comparison)
- Trying all possible splits (solved with enumeration from
max(0, k - n)
tomin(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 exactlyk
digitstop
: Current top position in the stackremain
: How many elements we can afford to skip (important for knowing when we can pop elements)
For each element x
in nums
:
- 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 - If the stack isn't full (
top + 1 < k
), we pushx
onto the stack - Otherwise, we skip
x
and decrementremain
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)
), returnFalse
- If
nums2
is exhausted (j >= len(nums2)
), returnTrue
- If
nums1[i] > nums2[j]
,nums1
is larger, returnTrue
- If
nums1[i] < nums2[j]
,nums2
is larger, returnFalse
- 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)
returnsTrue
, pick fromnums1
and incrementi
- Otherwise, pick from
nums2
and incrementj
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 leastk - n
fromnums1
ifnums2
can't provide allk
- Maximum from
nums1
:min(k, m)
- we can't take more thanm
(size ofnums1
) or more thank
total
For each valid split x
(taking x
from nums1
and k - x
from nums2
):
- Extract maximum subsequence of length
x
fromnums1
- Extract maximum subsequence of length
k - x
fromnums2
- Merge the two subsequences
- 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 EvaluatorExample 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:
- Tries different ways to split k between the two arrays
- Uses a greedy stack-based approach to extract maximum subsequences
- Merges subsequences optimally using lookahead comparison
- 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:
-
The main loop iterates from
l
tor
, which is at mostmin(k, m)
iterations, soO(k)
iterations. -
For each iteration:
- Function
f(nums1, x)
: TakesO(m)
time to extract subsequence of lengthx
fromnums1
- Function
f(nums2, k - x)
: TakesO(n)
time to extract subsequence of lengthk - x
fromnums2
- 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()
isO(k)
when arrays have many equal prefixes - Total merge time:
O(k²)
- Creates an array of size
- Array comparison
ans < arr
: TakesO(k)
time
- Function
-
Overall time complexity:
- Number of iterations:
O(k)
- Work per iteration:
O(m) + O(n) + O(k²) + O(k)
- Since
k ≤ m + n
, we haveO(k) ≤ O(m + n)
- Total:
O(k) × O(k²) = O(k³) = O((m + n)³)
in the worst case
- Number of iterations:
Space Complexity: O(m + n)
The space analysis:
- Function
f()
: Creates a stack of sizek
, soO(k)
space - Function
compare()
: Uses recursion with maximum depthO(min(len(nums1), len(nums2)))
, which isO(m + n)
in worst case - Function
merge()
: Creates an array of sizem + n
for merging, soO(m + n)
space - Main function: Stores
arr1
,arr2
,arr
, andans
, each of size at mostk
, soO(k)
space - Since
k ≤ m + n
, the dominant space complexity isO(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 = []
ornums2 = []
k = m + n
: Should return all elements merged optimallyk = 1
: Should return the single largest element from either array
In a binary min heap, the maximum element can be found in:
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Want a Structured Path to Master System Design Too? Don’t Miss This!