Description
You are given two sorted arrays:
nums1containsmvalid elements followed bynempty spaces (0s).nums2containsnsorted elements.
Your task is to merge nums2 into nums1 so that nums1 becomes one sorted array.
The important catch is that you must modify nums1 in-place. You cannot return a new array.
Example
Input:
nums1 = [1,2,3,0,0,0]
m = 3
nums2 = [2,5,6]
n = 3
Output:
[1,2,2,3,5,6]My Solution
from typing import List
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
i, j, last = m - 1, n - 1, m + n - 1
while i >= 0 and j >= 0:
if nums1[i] > nums2[j]:
nums1[last] = nums1[i]
i -= 1
else:
nums1[last] = nums2[j]
j -= 1
last -= 1
while j >= 0:
nums1[last] = nums2[j]
j -= 1
last -= 1Intuition
The obvious solution would be:
- Copy every element from
nums2into the empty spaces ofnums1. - Sort the entire array.
Although this works, sorting costs O((m+n) log(m+n)).
Since both arrays are already sorted, we should be able to do much better.
The challenge is that we're writing directly into nums1.
If we started merging from the beginning, we'd overwrite values in nums1 that we haven't compared yet.
Instead, we work backwards.
The Key Insight
The extra space in nums1 is already at the end.
nums1
[1,2,3,0,0,0]
^^^^^^
free spaceThe largest value among both arrays always belongs at the very end.
So instead of filling from left to right, we fill from right to left.
This means we never overwrite useful values.
Three Pointers
We maintain three pointers:
i -> last valid element in nums1
j -> last element in nums2
last -> final insertion positionInitially,
nums1 = [1,2,3,0,0,0]
i last
nums2 = [2,5,6]
ji = m - 1
j = n - 1
last = m + n - 1Step 1
Compare:
nums1[i] = 3
nums2[j] = 6Since 6 is larger,
[1,2,3,0,0,6]Move:
j--
last--Step 2
Compare
3 vs 55 is larger.
[1,2,3,0,5,6]Move pointers.
Step 3
Compare
3 vs 23 is larger.
[1,2,3,3,5,6]Move i.
Continue
Eventually,
[1,2,2,3,5,6]Everything ends up in the correct position without ever needing an additional array.
Why We Merge From the End
Suppose we tried writing from the front.
nums1 = [1,2,3,0,0,0]
nums2 = [2,5,6]After writing one element, we'd overwrite values in nums1 that we still need to compare later.
Working backwards avoids this completely because the empty slots are already at the end.
Why Do We Need the Second While Loop?
Our main loop stops when either array is exhausted.
while i >= 0 and j >= 0:There are two possibilities.
Case 1
nums2 finishes first.
Example:
nums1 = [4,5,6]
nums2 = [1,2,3]After merging,
[1,2,3,4,5,6]The remaining elements already in nums1 are already in the correct place.
Nothing else needs to be done.
Case 2
nums1 finishes first.
Example:
nums1 = [1]
nums2 = [2,3,4]After the first loop,
[1,2,3,4]There are still values left in nums2 that haven't been copied.
That's why we need:
while j >= 0:
nums1[last] = nums2[j]
j -= 1
last -= 1Notice there is no corresponding loop for nums1.
Any remaining values from nums1 are already exactly where they belong.
Edge Cases
Empty nums2
nums1 = [1]
nums2 = []Nothing changes.
Empty nums1
nums1 = [0]
nums2 = [1]The second loop copies everything from nums2.
Result:
[1]Duplicate Values
nums1 = [2,2,4]
nums2 = [2,3]The algorithm naturally handles duplicates since it always picks the larger element from the end.
Complexity Analysis
Time Complexity
Each pointer only moves in one direction.
Every element is processed at most once.
Time: O(m + n)
Space Complexity
Only three integer pointers are used.
No extra arrays are created.
Space: O(1)
Key Takeaways
- Since both arrays are already sorted, sorting again is unnecessary.
- The empty space at the end of
nums1makes it possible to merge in-place. - Filling the array from right to left prevents overwriting values that still need to be compared.
- The algorithm only requires three pointers and runs in linear time.
- If
nums2has remaining elements after the main loop, they must be copied over. Remaining elements innums1are already in the correct position and require no additional work.
