88. Merge Sorted Array
Problem Description
You have two sorted integer arrays nums1
and nums2
that are already arranged in non-decreasing order. You also have two integers m
and n
that tell you how many valid elements are in nums1
and nums2
respectively.
Your task is to merge nums2
into nums1
so that nums1
becomes one sorted array containing all elements from both arrays.
Here are the key requirements:
nums1
has a total length ofm + n
to accommodate all elements- The first
m
positions innums1
contain the valid elements to be merged - The last
n
positions innums1
are filled with zeros and serve as placeholder space nums2
has lengthn
and containsn
valid elements- The merge must be done in-place - you need to modify
nums1
directly rather than creating a new array - The final result should have all elements sorted in non-decreasing order
For example, if nums1 = [1,2,3,0,0,0]
with m = 3
and nums2 = [2,5,6]
with n = 3
, after merging, nums1
should become [1,2,2,3,5,6]
.
The solution uses a two-pointer approach that starts from the end of both arrays. By comparing elements from the back and placing the larger element at the end of nums1
, we can efficiently merge the arrays without needing extra space. The process continues until all elements from nums2
have been placed into nums1
.
Intuition
The key insight comes from recognizing that we have extra space at the end of nums1
that we can utilize. If we tried to merge from the beginning, we'd run into a problem: placing elements from nums2
at the front of nums1
would overwrite the original elements we haven't processed yet.
Think about it this way - if we start from the front and want to insert nums2[0]
into its correct position in nums1
, we might need to shift existing elements in nums1
to make room. This shifting would be complicated and inefficient.
However, if we work backwards from the end, we have a brilliant advantage: the end of nums1
is empty (filled with zeros)! We can safely place elements there without worrying about overwriting any unprocessed data.
By comparing the largest unprocessed elements from both arrays (which are at positions m-1
and n-1
initially), we can determine which element should go at the very end of the merged array. The larger of the two should occupy position m+n-1
. Then we move our pointers accordingly and repeat.
This backwards approach naturally handles the merging without requiring any extra space or complex shifting operations. Each element is placed exactly once in its final position. Even if all elements from nums2
are larger than those in nums1
, or vice versa, the algorithm handles these edge cases gracefully.
The beauty of this solution is that it transforms what could be a complex insertion problem into a simple comparison and placement problem, taking advantage of the pre-allocated space in nums1
.
Learn more about Two Pointers and Sorting patterns.
Solution Approach
The solution implements a two-pointer technique that processes both arrays from right to left. Here's how the algorithm works:
We initialize three pointers:
i = m - 1
: Points to the last valid element innums1
j = n - 1
: Points to the last element innums2
k = m + n - 1
: Points to the last position in the merged array (end ofnums1
)
The main loop continues as long as there are elements from nums2
to process (j >= 0
). In each iteration:
-
Compare elements: We check if there's still an element in
nums1
to compare (i >= 0
) and if that element is greater than the current element innums2
(nums1[i] > nums2[j]
) -
Place the larger element:
- If
nums1[i]
is larger, we place it at positionk
and decrementi
- Otherwise (including when
i < 0
), we placenums2[j]
at positionk
and decrementj
- If
-
Move the destination pointer: After placing an element, we decrement
k
to move to the next position
The algorithm naturally handles edge cases:
- When all elements from
nums1
have been processed (i < 0
), we simply copy the remaining elements fromnums2
- When all elements from
nums2
have been placed, the loop ends, and any remaining elements innums1
are already in their correct positions
This approach achieves O(m + n)
time complexity with only a single pass through both arrays, and O(1)
space complexity since we're modifying nums1
in-place without using any additional data structures.
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 walk through a concrete example to see how the algorithm works step by step.
Initial Setup:
nums1 = [4, 5, 6, 0, 0, 0]
,m = 3
nums2 = [1, 2, 3]
,n = 3
- We need to merge these into
nums1 = [1, 2, 3, 4, 5, 6]
Initialize pointers:
i = 2
(pointing tonums1[2] = 6
)j = 2
(pointing tonums2[2] = 3
)k = 5
(pointing to the last position innums1
)
Step 1:
- Compare
nums1[2] = 6
withnums2[2] = 3
- Since
6 > 3
, place6
at positionk = 5
nums1 = [4, 5, 6, 0, 0, 6]
- Update:
i = 1
,k = 4
Step 2:
- Compare
nums1[1] = 5
withnums2[2] = 3
- Since
5 > 3
, place5
at positionk = 4
nums1 = [4, 5, 6, 0, 5, 6]
- Update:
i = 0
,k = 3
Step 3:
- Compare
nums1[0] = 4
withnums2[2] = 3
- Since
4 > 3
, place4
at positionk = 3
nums1 = [4, 5, 6, 4, 5, 6]
- Update:
i = -1
,k = 2
Step 4:
- Now
i < 0
, so we only considernums2[2] = 3
- Place
3
at positionk = 2
nums1 = [4, 5, 3, 4, 5, 6]
- Update:
j = 1
,k = 1
Step 5:
- Still
i < 0
, placenums2[1] = 2
at positionk = 1
nums1 = [4, 2, 3, 4, 5, 6]
- Update:
j = 0
,k = 0
Step 6:
- Still
i < 0
, placenums2[0] = 1
at positionk = 0
nums1 = [1, 2, 3, 4, 5, 6]
- Update:
j = -1
,k = -1
Result:
The loop ends since j < 0
. All elements have been successfully merged in sorted order: nums1 = [1, 2, 3, 4, 5, 6]
This example demonstrates how working backwards prevents us from overwriting unprocessed elements and efficiently places each element exactly once in its final position.
Solution Implementation
1class Solution:
2 def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
3 """
4 Merge two sorted arrays in-place.
5 nums1 has enough space to hold elements from both arrays.
6
7 Args:
8 nums1: First sorted array with extra space at the end
9 m: Number of valid elements in nums1
10 nums2: Second sorted array to merge into nums1
11 n: Number of elements in nums2
12 """
13 # Start from the end of the merged array
14 write_index = m + n - 1
15
16 # Pointers to the last valid elements in each array
17 nums1_index = m - 1
18 nums2_index = n - 1
19
20 # Continue until all elements from nums2 are processed
21 while nums2_index >= 0:
22 # If nums1 still has elements and current element is larger than nums2's
23 if nums1_index >= 0 and nums1[nums1_index] > nums2[nums2_index]:
24 # Place the larger element from nums1 at the current position
25 nums1[write_index] = nums1[nums1_index]
26 nums1_index -= 1
27 else:
28 # Place the element from nums2 (either it's larger or nums1 is exhausted)
29 nums1[write_index] = nums2[nums2_index]
30 nums2_index -= 1
31
32 # Move the write position one step back
33 write_index -= 1
34
1class Solution {
2 public void merge(int[] nums1, int m, int[] nums2, int n) {
3 // Initialize three pointers:
4 // i: points to the last valid element in nums1
5 // j: points to the last element in nums2
6 // k: points to the last position in the merged array
7 int i = m - 1;
8 int j = n - 1;
9 int k = m + n - 1;
10
11 // Merge arrays from right to left
12 // Continue until all elements from nums2 are processed
13 while (j >= 0) {
14 // Compare elements from both arrays and place the larger one at position k
15 // If nums1 has remaining elements AND current element in nums1 is greater than nums2
16 if (i >= 0 && nums1[i] > nums2[j]) {
17 nums1[k] = nums1[i];
18 i--;
19 } else {
20 // Otherwise, take element from nums2
21 nums1[k] = nums2[j];
22 j--;
23 }
24 k--;
25 }
26 // Note: If i >= 0 after the loop, remaining elements in nums1 are already in place
27 }
28}
29
1class Solution {
2public:
3 void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
4 // Initialize three pointers:
5 // i: points to the last valid element in nums1's original part
6 // j: points to the last element in nums2
7 // k: points to the last position in the merged array
8 int i = m - 1;
9 int j = n - 1;
10 int k = m + n - 1;
11
12 // Continue merging while there are elements in nums2 to process
13 while (j >= 0) {
14 // Compare elements from both arrays and place the larger one at position k
15 // If nums1 has elements left (i >= 0) and nums1[i] is greater than nums2[j],
16 // place nums1[i] at position k and decrement i
17 // Otherwise, place nums2[j] at position k and decrement j
18 if (i >= 0 && nums1[i] > nums2[j]) {
19 nums1[k] = nums1[i];
20 i--;
21 } else {
22 nums1[k] = nums2[j];
23 j--;
24 }
25 k--;
26 }
27 // Note: If elements remain in nums1 after nums2 is exhausted,
28 // they are already in their correct positions
29 }
30};
31
1/**
2 * Merges two sorted arrays into the first array in-place.
3 * The first array has enough space to hold all elements from both arrays.
4 *
5 * @param nums1 - First sorted array with extra space at the end to hold nums2 elements
6 * @param m - Number of valid elements in nums1 (excluding extra space)
7 * @param nums2 - Second sorted array to be merged into nums1
8 * @param n - Number of elements in nums2
9 * @returns void - Modifies nums1 in-place
10 */
11function merge(nums1: number[], m: number, nums2: number[], n: number): void {
12 // Initialize three pointers:
13 // - firstArrayIndex: Points to the last valid element in nums1
14 // - secondArrayIndex: Points to the last element in nums2
15 // - mergePosition: Points to the last position in the merged array
16 let firstArrayIndex: number = m - 1;
17 let secondArrayIndex: number = n - 1;
18 let mergePosition: number = m + n - 1;
19
20 // Merge from the end of both arrays, working backwards
21 // Continue until all elements from nums2 are processed
22 while (secondArrayIndex >= 0) {
23 // Compare elements from both arrays and place the larger one at mergePosition
24 // If nums1 has remaining elements AND current nums1 element is greater than nums2 element
25 if (firstArrayIndex >= 0 && nums1[firstArrayIndex] > nums2[secondArrayIndex]) {
26 // Place nums1 element at current merge position and move firstArrayIndex left
27 nums1[mergePosition] = nums1[firstArrayIndex];
28 firstArrayIndex--;
29 } else {
30 // Place nums2 element at current merge position and move secondArrayIndex left
31 nums1[mergePosition] = nums2[secondArrayIndex];
32 secondArrayIndex--;
33 }
34
35 // Move merge position to the left for next iteration
36 mergePosition--;
37 }
38}
39
Time and Space Complexity
The time complexity is O(m + n)
, where m
is the number of initialized elements in nums1
and n
is the number of elements in nums2
. The algorithm iterates through all elements from nums2
(controlled by the condition j >= 0
), and for each element, it performs a constant-time comparison and assignment operation. In the worst case, it also processes all m
elements from nums1
. Therefore, the total number of operations is proportional to m + n
.
The space complexity is O(1)
. The algorithm modifies nums1
in-place without using any additional data structures. It only uses a fixed number of variables (k
, i
, and j
) regardless of the input size, resulting in constant space usage.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Starting from the Beginning Instead of the End
A common mistake is attempting to merge from the beginning of the arrays. This approach seems intuitive but causes problems:
Incorrect Approach:
def merge_wrong(nums1, m, nums2, n):
i, j, k = 0, 0, 0
while i < m and j < n:
if nums1[i] <= nums2[j]:
# This overwrites valid elements!
nums1[k] = nums1[i]
i += 1
else:
nums1[k] = nums2[j]
j += 1
k += 1
Why it fails: When merging from the front, you risk overwriting elements in nums1
that haven't been processed yet. Since we need to preserve all valid elements, this leads to data loss.
Solution: Always merge from the end to avoid overwriting unprocessed elements. The empty space at the end of nums1
provides a safe zone to place elements without losing data.
Pitfall 2: Incorrect Loop Termination Condition
Another frequent error is using the wrong condition for the main loop:
Incorrect Implementation:
def merge_wrong(nums1, m, nums2, n):
i, j, k = m - 1, n - 1, m + n - 1
# Wrong: stops when either array is exhausted
while i >= 0 and j >= 0:
if nums1[i] > nums2[j]:
nums1[k] = nums1[i]
i -= 1
else:
nums1[k] = nums2[j]
j -= 1
k -= 1
# Need additional code to handle remaining elements
Why it fails: This stops as soon as one array is exhausted, requiring additional logic to handle remaining elements. If nums1
is exhausted first, you need to copy remaining elements from nums2
. If nums2
is exhausted first, the remaining nums1
elements are already in place.
Solution: Use while j >= 0
as the loop condition. This ensures all elements from nums2
are processed. If nums1
elements remain after nums2
is exhausted, they're already in their correct positions.
Pitfall 3: Off-by-One Errors with Indices
Index management is crucial, and off-by-one errors are easy to make:
Common Mistakes:
# Wrong: Using length instead of last index i = m # Should be m - 1 j = n # Should be n - 1 # Wrong: Incorrect array bounds k = m + n # Should be m + n - 1 # Wrong: Forgetting to decrement after placement nums1[k] = nums2[j] # Missing: j -= 1 or k -= 1
Solution: Remember that array indices are 0-based. The last valid element in nums1
is at index m - 1
, not m
. Similarly, the last position in the merged array is m + n - 1
. Always decrement the appropriate pointer after using it.
Pitfall 4: Not Handling Empty Arrays
Failing to consider edge cases where one array might be empty:
Problematic Assumption:
def merge_unsafe(nums1, m, nums2, n):
# Assumes both arrays have elements
if nums1[m-1] <= nums2[0]:
# Just append nums2 to nums1
for i in range(n):
nums1[m + i] = nums2[i]
return
Why it fails: This crashes when m = 0
(accessing nums1[-1]
) or n = 0
(accessing nums2[0]
).
Solution: The main algorithm naturally handles these cases. When m = 0
, the condition i >= 0
is immediately false, so all elements come from nums2
. When n = 0
, the loop while j >= 0
never executes, leaving nums1
unchanged.
Consider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80]
is 4
and LIS is
[3, 7, 40, 80]
.
What is the recurrence relation?
Recommended Readings
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
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donโt Miss This!