Merge Two Sorted Arrays Without Extra Space in Java

Two arrays containing integers are given to us. Both arrays are sorted in ascending order. Our task is to display all of the elements of both the sorted arrays such that all the elements are displayed in ascending order. Note that the use of any extra space for storing the elements is not allowed. In other words, the space complexity of the program should always be constant.

Example 1:

Input

int inArr1[] = {1, 3, 7, 9, 15, 20}
int inArr2[] = {2, 5, 8, 11, 14, 16, 18, 19}

Output

{1, 2, 3, 5, 7, 8, 9, 11, 14, 15, 16, 18, 19, 20}

Explanation:

When we merge both arrays in ascending order, we get the above output.

Example 2:

Input

int inArr1[] = {-11, -5, -3, 1, 5, 15, 25, 30, 40, 45}
int inArr2[] = {-30, -25, -19, -10, -1, 7, 20, 29, 35}

Output

{-30, -25, -19, -11, -10, -5, -3, -1, 1, 5, 7, 15, 20, 25, 29, 30, 35, 40, 45}

Explanation:

When we merge both arrays in ascending order, we get the above output.

Simple Approach: Brute Force and Sorting

If it is allowed to use extra space, we can easily create one array whose size will be equal to the sum of sizes of the input arrays, and then, using two pointers, we can put all the elements in the constructed array in ascending order. However, the problem statement says extra space is not allowed. Therefore, we have to use the space of the input array. All we have to do is to put the first m smallest elements in the first input array. The rest of the n elements should be put in the second input array. The apply sorting on the second array, as the first will be sorted in this process. After that, using a for-loop displays all of the elements present in both arrays. Note that here m = size of the first input array and n = size of the second input array.

Algorithm

  • for 'j' from 0 to 'm - 1':
    • flg = j
    • least = inArr1[j]
    • for 'k' from 'j' to 'm - 1':
      • if(inArr1[k] < least):
        • least = inArr1[k]
        • flg = k
      • for 'k' from '0' to 'n - 1':
      • if(inArr2[k] < least):
      • least = inArr2[k]
      • flg = k
    • Swap 'inArr1[j]' with the 'least' element.
  • Sort the array 'inArr2'.

Now observe the following program.

FileName: MergeSortedArr.java

Output:

The input arrays are: 
1 3 7 9 15 20 
2 5 8 11 14 16 18 19 
After merging the sorted arrays, we get: 
1 2 3 5 7 8 9 11 14 15 16 18 19 20 

The input arrays are: 
-11 -5 -3 1 5 15 25 30 40 45 
-30 -25 -19 -10 -1 7 20 29 35 
After merging the sorted arrays, we get: 
-30 -25 -19 -11 -10 -5 -3 -1 1 5 7 15 20 25 29 30 35 40 45

Complexity Analysis: The first array inArr1[] is getting traversed in O(m) time, and for every element in inArr1[], we are traversing the rest of the positions of inArr1[], and all of the positions of inArr2[]. Therefore, the overall time complexity of the program is O(m x (m + n)). The program is not using extra space. Thus, the space complexity of the program is O(1).

Another Approach: Using Two Pointers and Sorting

Here, we will be keeping the 'm' smallest numbers in 'inArr1[]' (may or may not be sorted) and the rest elements in 'inArr2[]' (may or may not be sorted). We iterate over 'inArr1[]' and 'inArr2[]' with the help of two pointers 'p' = 0 and 'q' = 0, respectively. If 'inArr1[p]' is equal to or larger than 'inArr2[q]', the we do the swapping of some 'kth' element of 'inArr1[]' with 'inArr2[q]' and increment 'q', otherwise, we increment 'p'. It does make sense to replace the largest elements of 'inArr1[]'. Therefore, we initialize 'k' = 'm - 1'. After that, we decrease 'k' after every swap. In the end, we sort 'inArr1[]' and 'inArr2[]'.

Algorithm:

  • p = 0, q = 0, k = m - 1
  • while(p <= k and q < N):
    • if(inArr1[p] < inArr2[q]):
      • p++
    • else
      • swapEle(inArr1[k], inArr2[q])
      • q = q + 1
      • k = k - 1
  • Sort 'inArr1[]' in non-decreasing order.
  • Sort 'inArr2[]' in non-decreasing order.

Now, observe the following implementation.

FileName: MergeSortedArr1.java

Output:

The input arrays are: 
1 3 7 9 15 20 
2 5 8 11 14 16 18 19 
After merging the sorted arrays, we get: 
1 2 3 5 7 8 9 11 14 15 16 18 19 20 

The input arrays are: 
-11 -5 -3 1 5 15 25 30 40 45 
-30 -25 -19 -10 -1 7 20 29 35 
After merging the sorted arrays, we get: 
-30 -25 -19 -11 -10 -5 -3 -1 1 5 7 15 20 25 29 30 35 40 45

Complexity Analysis: We are traversing over 'inArr1[]' and 'inArr2[]' in O(m + n) time, and the swapping of elements takes O(1) time. The sorting of both the arrays 'inArr1[]' and 'inArr2[]' take O(m x log(m)) and O(n x log(n)) time, respectively. Therefore, the overall time complexity of the above program is O(m + n + m x log(m) + n x log(n)). The space complexity of the program is O(1), as no extra space is utilized.

Another Approach: Using Euclid's Division Lemma

In this approach, we will perform the swaps in such a way that it is not required to do the sorting of the arrays in the end. We will be filling the smallest numbers in the smallest positions rather than putting them into the last positions like in Approach 2 (i.e., we will start with 'k' = 0 instead of 'k' = 'm - 1'). However, in this case, it is possible that a number from 'inArr1[]' is getting replaced by some of the numbers from the second array 'inArr2[]'. However, there are fair chances that it is getting utilized at some later position in 'inArr1[]'. Therefore, it is required to extract the number that was initially positioned at a position 'k' in case it was replaced earlier.

In the following way, we can use Euclid's Lemma:

From a number M = X * R + P, we can get 'X' by dividing 'M' by 'R' and 'P' by taking the modulo of 'M' by 'R'.

We choose 'MAX' = 109 + 1 as 'R' for the complete array 'inArr1[]'.

We iterate through elements of 'inArr1[]', and 'inArr2[]' with the help of two pointers 'p' and 'q'. Initialize the positions by 'k' = 0. We do check for the initially placed numbers at 'inArr1[p]' and 'inArr2[q]'. We compare them and increase 'inArr1[k]' or 'inArr2[k - m]' by the product of the minimum of the two original numbers and 'MAX'. We increase 'q' if the number from 'inArr2[]' is smaller. Otherwise, we increase 'p'.

Similarly, we update the values for all 'k' from 0 to 'm + n - 1'.

In the end, we iterate through 'inArr1[]' and 'inArr2[]' and divide all of the numbers by 'R' to get the merged array.

Algorithm

  • Assign MAX = 109 + 1
  • p = 0, q = 0, k = m - 1
  • while(p < m and q < n and k < m + n):
    • orgNum1 = inArr1[p] % MAX
    • orgNum2 = inArr2[q] % MAX
    • if(orgNum1 <= orgNum2):
      • if(k < m):
        • inArr1[k] = inArr1[k] + (orgNum1 * MAX)
      • else:
        • inArr2[k - m] = inArr2[k - m] + (orgNum1 * MAX)
      • p = p + 1
      • k = k + 1
    • else:
      • if(k < m):
      • inArr1[k] = inArr1[k] + orgNum2 * MAX
      • else:
        • inArr2[k - m] = inArr2[k - m] + orgNum2 * MAX
      • q = q + 1
      • k = k + 1
    • while(q < n):
      • orgNum2 = inArr2[q] % MAX
      • if(k < m):
        • inArr1[k] += orgNum2 * MAX
      • else:
        • inArr2[k - m] += orgNum2 * MAX
      • q = q + 1
      • k = k + 1
    • while(p < m):
      • orgNum1 = inArr1[p] % MAX
      • if(k < m):
        • inArr1[k] = inArr1[k] + (orgNum1 * MAX)
      • else:
        • inArr2[k - m] = inArr2[k - m] + orgNum2 * MAX
      • p = p + 1
      • k = k + 1
    • for 'p' from 0 to 'm - 1':
      • inArr1[p] = inArr1[p] / MAX
    • for 'q' from 0 to 'n - 1':
      • inArr2[q] = inArr2[q] / MAX

Now, let's observe the following implementation.

FileName: MergeSortedArr2.java

Output:

The input arrays are: 
1 3 7 9 15 20 
2 5 8 11 14 16 18 19 
After merging the sorted arrays, we get: 
1 2 3 5 7 8 9 11 14 15 16 18 19 20 

Complexity Analysis: In the program, both the input arrays are traversed only once. Therefore, the total time complexity of the program is O(m + n), where 'm' is the size of the input array 'inArr1[]' and 'n' is the size of the input array 'inArr2[]'. The space complexity of the program is the same as the previous program.

Note: The above program does not work properly for the input arrays containing negative elements.