LeetCode 88: Merge Sorted Array

How I solved the Merge Sorted Array problem with linear runtime and constant auxiliary space complexity.

5 min read

Challenge Description

You can view the challenge here.

You are given two integer arrays, nums1 and nums2, sorted in non-decreasing order. The integers m and n represent the number of meaningful elements in nums1 and nums2, respectively.

Merge the arrays into a single sorted array and store the result in nums1. The function should not return the result.

nums1 has a length of m + n, where its first m elements contain the values to merge, while its final n elements are placeholders set to 0 and should be ignored. nums2 has a length of n.

For example:

  • nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], and n = 3 produces [1,2,2,3,5,6].
  • nums1 = [1], m = 1, nums2 = [], and n = 0 produces [1].
  • nums1 = [0], m = 0, nums2 = [1], and n = 1 produces [1]. The 0 is only a placeholder because nums1 must have enough space for the result.

The constraints are 0 <= m, n <= 200, 1 <= m + n <= 200, and every array value is between -10^9 and 10^9. The follow-up is to solve the problem in O(m+n) time.

Intuition

This challenge is easy to solve, but achieving a runtime of O(m+n)\mathcal{O}(m+n) and an auxiliary space complexity of O(1)\mathcal{O}(1) is a bit more challenging.

I wanted to learn C++, so I tried to solve this problem in C++ with a runtime of O(m+n)\mathcal{O}(m+n):

  
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
// Initialize the collector here.
vector<int> temp(m + n, 0);
int counter1 = 0;
int counter2 = 0;
for (int index = 0; index < m + n; index++) {
// First, check that both counters are valid, then compare the values.
if (counter2 < n && counter1 < m &&
nums1[counter1] > nums2[counter2]) {
temp[index] = nums2[counter2];
counter2++;
}
// In this case, either nums2[counter2] >= nums1[counter1],
// or one of the arrays is exhausted.
// This is the case when nums1 is not yet exhausted and
// nums2[counter2] >= nums1[counter1].
else if (counter1 < m) {
temp[index] = nums1[counter1];
counter1++;
}
// This is when nums2 has more elements.
// We could check counter2 < n here, but it is implied by
// the preceding cases.
else {
temp[index] = nums2[counter2];
counter2++;
}
}
// Set nums1 to the actual answer.
nums1 = temp;
}
};

However, this results in O(m+n)\mathcal{O}(m + n) auxiliary space, and I feel that we can do better. The fact that nums1 already has m+nm + n elements seems like a clue that an optimization is possible.

To achieve O(1)\mathcal{O}(1) auxiliary space, I cannot create an additional array to store the temporary solution. This means that I need to swap values in nums1. I tried a similar approach: I iterate over the indices and swap the smallest values between nums1 and nums2 at the current indices. This resulted in the following incorrect code:

  
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int counterNums2 = 0;
for (int index = 0; index < m + n && counterNums2 < n; index++) {
// Check whether the current value in nums2 is larger than
// the next one. If so, swap them.
if (counterNums2 + 1 < n && nums2[counterNums2] > nums2[counterNums2 + 1]) {
int temp = nums2[counterNums2 + 1];
nums2[counterNums2 + 1] = nums2[counterNums2];
nums2[counterNums2] = temp;
}
// Swap in place if nums1 is larger than nums2, or fill the
// rest of nums1 with the remaining values from nums2.
if (nums1[index] > nums2[counterNums2] || index >= m) {
int temp = nums2[counterNums2];
nums2[counterNums2] = nums1[index];
nums1[index] = temp;
if (index >= m) {
counterNums2++;
}
}
}
}
};

This code does not work because I lose the sorted-array invariant for nums2. To fix it, I could sort nums2 after every swap, but that would result in a time complexity of O((m+n)(nlogn))\mathcal{O}((m+n)(n \log n)).

So the question is whether there is a way to write the result in place without breaking the invariant that both nums1 and nums2 are sorted. Then I realized that the extra space in nums1 could act as auxiliary space for storing the partial answer. I can use this space first to save space.

Approach

The unintuitive part of the approach is solving the problem in reverse order. I use the same intuition as before, but write the result in place. Because m+nmm + n \geq m (the size of nums1 is greater than or equal to the number of its actual elements), I can safely iterate from the largest indices downward and store the temporary result without worrying that it will overwrite an actual value in nums1.

Proof

Let c1c_1, c2c_2, and ii denote the indices of the largest unmerged elements in nums1 and nums2, and the next output position, respectively. I initialize them as follows:

  • c1=m1c_1 = m - 1
  • c2=n1c_2 = n - 1
  • i=m+n1i = m + n - 1

Each iteration decreases ii by one, and exactly one of c1c_1 or c2c_2 also decreases by one. This means that i=c1+c2+1i = c_1 + c_2 + 1 at all times.

I was worried that I might overwrite nums1[c1] during an iteration, so I checked whether that could happen.

As long as nums2 has an unmerged element (c20c_2 \geq 0), ic1+1>c1i \geq c_1 + 1 > c_1. Therefore, writing an element to nums1[i] cannot overwrite the unread element at nums1[c_1].

If nums2 is exhausted, then c2=1c_2 = -1 and i=c1i = c_1, which means that the remaining elements of nums1 are already in their final positions.

Complexity

  • Time complexity: O(m+n)\mathcal{O}(m+n)

  • Space complexity: O(1)\mathcal{O}(1)

Code

  
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
// Start from the last indices.
int counter1 = m - 1;
int counter2 = n - 1;
// Start from the end of the array.
for (int index = m + n - 1; index >= 0; index--) {
// Use the same logic as before, but fill the array with
// the largest values first.
if (counter1 >= 0 && counter2 >= 0 &&
nums1[counter1] < nums2[counter2]) {
nums1[index] = nums2[counter2];
counter2--;
} else if (counter1 >= 0) {
nums1[index] = nums1[counter1];
counter1--;
} else {
nums1[index] = nums2[counter2];
counter2--;
}
}
}
};