LeetCode 4: Median of Two Sorted Arrays

Difficulty: Hard

Language: Java

Topics: Arrays, Two Pointers, Binary Search
Optimal Time Complexity: O(log(min(m, n)))
Optimal Space Complexity: O(1)


Introduction

Median of Two Sorted Arrays is LeetCode Problem #4 and is one of the most popular binary-search problems asked in coding interviews.

At first, the problem looks simple. We have two sorted arrays, so we might think:

“Why don’t we just combine them and find the middle?”

That approach works, but it does not satisfy the optimal time-complexity requirement.

The real challenge is to solve the problem without actually merging the arrays.

In this article, we’ll understand the problem step by step and move from:

Brute Force → Better Approach → Optimal Binary Search


Problem Statement

Given two sorted arrays nums1 and nums2, return the median of the two sorted arrays.

The overall runtime complexity should be:

O(log(m + n))

where m and n are the lengths of the two arrays.

Example 1
nums1 = [1, 3]
nums2 = [2]

If we combine the arrays:

[1, 2, 3]

There are 3 elements.

The middle element is:

2

Therefore:

 
Answer = 2

Example 2

nums1 = [1, 3]
nums2 = [2, 4]

Combined:

[1, 2, 3, 4]

There are 4 elements, so there are two middle elements:

2 and 3

Therefore:

Median = (2 + 3) / 2
       = 2.5

Answer = 2.5

 First Understand What a Median Is

Before solving the problem, let’s understand the median.
Case 1: Odd Number of Elements

Consider:

[1, 2, 3, 4, 5]

There are 5 elements.

The middle element is:

3

Therefore:

Median = 3

Case 2: Even Number of Elements

Consider:

[1, 2, 3, 4]

There are 4 elements.

The two middle elements are:

2 and 3

Therefore:

Median = (2 + 3) / 2
       = 2.5

Simple Rule

For an odd number of elements:

Median = Middle element

For an even number of elements:

Median = Average of the two middle elements

Approach 1 — Brute Force

The easiest solution is to actually combine both arrays.

For example:

nums1 = [1, 3]
nums2 = [2, 4]

We create:

merged = [1, 2, 3, 4]

Then we find the median.

Because both arrays are already sorted, we can merge them using the same technique used in the Merge Sort algorithm.

 Brute Force Java Solution

class Solution {

    public double findMedianSortedArrays(int[] nums1, int[] nums2) {

        int m = nums1.length;
        int n = nums2.length;

        int[] merged = new int[m + n];

        int i = 0;
        int j = 0;
        int k = 0;

        // Merge both sorted arrays
        while (i < m && j < n) {

            if (nums1[i] <= nums2[j]) {
                merged[k] = nums1[i];
                i++;
            } else {
                merged[k] = nums2[j];
                j++;
            }

            k++;
        }

        // Remaining elements of nums1
        while (i < m) {
            merged[k] = nums1[i];
            i++;
            k++;
        }

        // Remaining elements of nums2
        while (j < n) {
            merged[k] = nums2[j];
            j++;
            k++;
        }

        // Find median
        int total = m + n;

        if (total % 2 == 1) {
            return merged[total / 2];
        }

        return (merged[total / 2 - 1]
                + merged[total / 2]) / 2.0;
    }
}

 Brute Force Dry Run

Let’s take:

nums1 = [1, 3]
nums2 = [2, 4]

Initially:

i = 0
j = 0
k = 0

We compare:

nums1[i] = 1
nums2[j] = 2

Since:

1 < 2

we put 1 into the merged array.

merged = [1]

Move i:

i = 1

Next:

nums1[i] = 3
nums2[j] = 2

Since 2 is smaller:

merged = [1, 2]

Move j:

j = 1

Next:

nums1[i] = 3
nums2[j] = 4

So we take 3:

merged = [1, 2, 3]

Finally, 4 is remaining:

merged = [1, 2, 3, 4]

Now:

total = 4

Since 4 is even, we take:

merged[1] = 2
merged[2] = 3

Therefore:

Median = (2 + 3) / 2.0
       = 2.5

Complexity of Brute Force

We visit all the elements while merging.Time Complexity

O(m + n)

Space Complexity

We create a new array containing all elements.

O(m + n)

This solution is easy to understand, but it is not optimal.

The problem asks for a logarithmic solution.


Approach 2 — Two Pointers Without Creating a New Array

Can we improve the brute-force solution?

Yes.

We don’t actually need to store the complete merged array.

We only need to reach the middle element or the two middle elements.

So we can use two pointers:

i → nums1
j → nums2

At every step, we take the smaller element.

We continue only until we reach the middle.

This removes the extra array.

Two-Pointer Java Solution

class Solution {

    public double findMedianSortedArrays(int[] nums1, int[] nums2) {

        int m = nums1.length;
        int n = nums2.length;

        int total = m + n;

        int i = 0;
        int j = 0;

        int previous = 0;
        int current = 0;

        // Move only until the middle
        for (int count = 0; count <= total / 2; count++) {

            previous = current;

            if (i < m && (j >= n || nums1[i] <= nums2[j])) {

                current = nums1[i];
                i++;

            } else {

                current = nums2[j];
                j++;
            }
        }

        // Odd number of elements
        if (total % 2 == 1) {
            return current;
        }

        // Even number of elements
        return (previous + current) / 2.0;
    }
}

Why Do We Need previous and current?

Consider:

[1, 2, 3, 4]

The middle elements are:

2 and 3

When our pointers move through the arrays:

1 → 2 → 3

When we reach 3:

previous = 2
current = 3

So for an even number of elements:

Median = (previous + current) / 2.0

For an odd number of elements, current itself is the median.


Complexity of the Two-Pointer Approach

We still move through the arrays until the middle.

Therefore:

Time Complexity

O(m + n)

Space Complexity

We only use a few variables.

O(1)

This is better than the brute-force solution because we have reduced the space from O(m + n) to O(1).

But the time complexity is still not logarithmic.

Now let’s move to the optimal solution.

Approach 3 — Optimal Binary Search

This is the most important approach.

The key idea is:

We don’t need to merge the arrays. We only need to find the correct partition.

Imagine dividing all the elements into two halves:

LEFT HALF | RIGHT HALF

For example:

[1, 2] | [3, 4]

The left half contains the smaller elements.

The right half contains the larger elements.

If we can find this partition directly, we can find the median without merging the arrays.


Partitioning the Two Arrays

Suppose:

nums1 = [1, 3]
nums2 = [2, 4]

We want to divide them like this:

nums1 = [1 | 3]

nums2 = [2 | 4]

Together:

LEFT  = [1, 2]

RIGHT = [3, 4]

So the combined structure is:

[1, 2] | [3, 4]

This is the correct partition.


Understanding the Four Important Values

Let’s give names to the values around the partitions:

nums1 = [ Aleft | Aright ]

nums2 = [ Bleft | Bright ]

For:

nums1 = [1 | 3]
nums2 = [2 | 4]

we have:

Aleft  = 1
Aright = 3

Bleft  = 2
Bright = 4

These four values are the most important part of the optimal solution.

When Is the Partition Correct?

The partition is correct when:

Aleft <= Bright

AND:

Bleft <= Aright

For our example:

Aleft = 1
Bright = 4

1 <= 4 ✓

And:

Bleft = 2
Aright = 3

2 <= 3 ✓

Both conditions are true.

Therefore, the partition is correct.


Why Do We Only Check These Two Conditions?

Both arrays are already sorted.

Therefore, inside nums1:

Aleft <= Aright

is automatically true.

And inside nums2:

Bleft <= Bright

is automatically true.

The only possible problem is between the two arrays.

So we only need to check:

Aleft <= Bright

and:

Bleft <= Aright

If both are true, we have successfully divided the combined array into two sorted halves.


How Many Elements Should Be on the Left?

Suppose:

m = nums1.length
n = nums2.length

The total number of elements is:

m + n

We calculate:

int half = (m + n + 1) / 2;

For example:

m = 2
n = 2

Then:

half = (2 + 2 + 1) / 2
     = 5 / 2
     = 2

Therefore, we need 2 elements on the left.


Why Do We Add +1?

The +1 helps us handle odd-length arrays.

Consider:

nums1 = [1, 3]
nums2 = [2]

Combined:

[1, 2, 3]

There are 3 elements.

We want:

[1, 2] | [3]

The left side has 2 elements.

Our formula gives:

half = (3 + 1) / 2
     = 2

Therefore, the extra element goes to the left side.

This makes the odd and even cases easier to handle.


Why Do We Binary Search Only One Array?

Suppose we choose a partition in nums1.

Let:

i = number of elements taken from nums1

If we know the total number of elements required on the left, then the partition in nums2 is automatically determined.

If:

i + j = half

then:

j = half - i

Therefore:

int j = half - i;

This is one of the most important lines in the solution.

Once we choose i, we automatically know j.

That’s why we only need to binary search one array.

Why Binary Search the Smaller Array?

Before starting binary search, we make sure that nums1 is the smaller array.

if (nums1.length > nums2.length) {
    return findMedianSortedArrays(nums2, nums1);
}

After this:

nums1.length <= nums2.length

Now the binary search operates on the smaller array.

This gives us:

O(log(min(m, n)))

time complexity.


Binary Search Range

We use:

int left = 0;
int right = m;

Why?

Suppose:

nums1 = [1, 3]

Possible partitions are:

| 1 3

1 | 3

1 3 |

Their partition positions are:

0
1
2

Since:

m = 2

our search range is:

0 to 2

Therefore:

left = 0;
right = m;

Finding the Partition

Inside the binary search:

int i = (left + right) / 2;

Suppose:

left = 0
right = 2

Then:

i = (0 + 2) / 2
  = 1

So:

nums1 = [1 | 3]

Now calculate the partition in nums2:

int j = half - i;

If:

half = 2
i = 1

then:

j = 2 - 1
  = 1

Therefore:

nums1 = [1 | 3]

nums2 = [2 | 4]

Finding Aleft and Aright

We need:

Aleft
Aright

The code is:

int Aleft = (i == 0)
        ? Integer.MIN_VALUE
        : nums1[i - 1];

int Aright = (i == m)
        ? Integer.MAX_VALUE
        : nums1[i];

Normally:

Aleft = nums1[i - 1]
Aright = nums1[i]

For:

nums1 = [1 | 3]

we get:

Aleft = 1
Aright = 3

Why Integer.MIN_VALUE?

Consider this partition:

| 1 3

There is no element on the left.

Therefore, Aleft doesn’t exist.

Instead of writing complicated special cases, we use:

Integer.MIN_VALUE

This behaves like negative infinity for our comparison.

So:

Aleft = -∞

Why Integer.MAX_VALUE?

Now consider:

1 3 |

There is no element on the right.

So we use:

Integer.MAX_VALUE

This behaves like positive infinity.

Therefore:

Aright = +∞

This allows the same logic to work even when the partition is at the beginning or end of an array.

Finding Bleft and Bright

We do the same thing for nums2:

int Bleft = (j == 0)
        ? Integer.MIN_VALUE
        : nums2[j - 1];

int Bright = (j == n)
        ? Integer.MAX_VALUE
        : nums2[j];

For:

nums2 = [2 | 4]

we get:

Bleft = 2
Bright = 4

Now we have:

Aleft  = 1
Aright = 3

Bleft  = 2
Bright = 4

The Complete Optimal Java Solution

class Solution {

    public double findMedianSortedArrays(int[] nums1, int[] nums2) {

        // Always binary search on the smaller array
        if (nums1.length > nums2.length) {
            return findMedianSortedArrays(nums2, nums1);
        }

        int m = nums1.length;
        int n = nums2.length;

        // Binary search range
        int left = 0;
        int right = m;

        // Number of elements needed on the left
        int half = (m + n + 1) / 2;

        while (left <= right) {

            // Partition nums1
            int i = (left + right) / 2;

            // Partition nums2
            int j = half - i;

            // Boundary values of nums1
            int Aleft = (i == 0)
                    ? Integer.MIN_VALUE
                    : nums1[i - 1];

            int Aright = (i == m)
                    ? Integer.MAX_VALUE
                    : nums1[i];

            // Boundary values of nums2
            int Bleft = (j == 0)
                    ? Integer.MIN_VALUE
                    : nums2[j - 1];

            int Bright = (j == n)
                    ? Integer.MAX_VALUE
                    : nums2[j];

            // Correct partition
            if (Aleft <= Bright && Bleft <= Aright) {

                // Odd number of elements
                if ((m + n) % 2 == 1) {
                    return Math.max(Aleft, Bleft);
                }

                // Even number of elements
                return (Math.max(Aleft, Bleft)
                        + Math.min(Aright, Bright)) / 2.0;
            }

            // Partition in nums1 is too far right
            else if (Aleft > Bright) {
                right = i - 1;
            }

            // Partition in nums1 is too far left
            else {
                left = i + 1;
            }
        }

        return 0.0;
    }
}

Complete Dry Run of the Optimal Solution

Let’s use:

nums1 = [1, 3]
nums2 = [2, 4]
Step 1 — Lengths
m = 2
n = 2

Both arrays have the same size, so no swapping is needed.


Step 2 — Initialize Binary Search

left = 0
right = 2

Calculate:

half = (2 + 2 + 1) / 2
     = 2

So we need 2 elements on the left.


Step 3 — Find i
i = (left + right) / 2

i = (0 + 2) / 2

i = 1

Therefore:

nums1 = [1 | 3]

Step 4 — Find j
j = half - i

j = 2 - 1

j = 1

Therefore:

nums2 = [2 | 4]

Step 5 — Boundary Values
Aleft  = 1
Aright = 3

Bleft  = 2
Bright = 4

Step 6 — Check the Partition

First condition:

Aleft <= Bright

1 <= 4 ✓

Second condition:

Bleft <= Aright

2 <= 3 ✓

Both conditions are true.

Therefore:

We found the correct partition.

The combined array can be thought of as:

[1, 2] | [3, 4]

Step 7 — Calculate the Median

Total number of elements:

m + n = 4

4 is even.

The largest element on the left is:

max(Aleft, Bleft)

max(1, 2)

= 2

The smallest element on the right is:

min(Aright, Bright)

min(3, 4)

= 3

Therefore:

Median = (2 + 3) / 2.0

       = 2.5

Final Answer = 2.5


What Happens If the Partition Is Wrong?

There are two possibilities.

Case 1 — Partition Is Too Far Right

Suppose:

Aleft > Bright

For example:

Aleft = 8
Bright = 5

That means we have taken too many elements from nums1.

The partition in nums1 needs to move left.

Therefore:

right = i - 1;

Case 2 — Partition Is Too Far Left

Suppose:

Bleft > Aright

For example:

Bleft = 10
Aright = 4

That means we have taken too few elements from nums1.

We need to move the partition in nums1 to the right.

Therefore:

left = i + 1;

This is the binary-search part of the algorithm.


Odd Number of Elements

Consider:

nums1 = [1, 3]
nums2 = [2]

Combined:

[1, 2, 3]

Correct partition:

[1, 2] | [3]

The left side contains the extra element.

Therefore, the median is the largest element on the left:

max(Aleft, Bleft)

In this case:

max(1, 2) = 2

So:

Median = 2


Even Number of Elements

Consider:

nums1 = [1, 3]
nums2 = [2, 4]

Correct partition:

[1, 2] | [3, 4]

The two middle elements are:

2 and 3

The largest element on the left:

max(Aleft, Bleft)

The smallest element on the right:

min(Aright, Bright)

Therefore:

Median =
(max(Aleft, Bleft) + min(Aright, Bright)) / 2.0

Why Do We Use 2.0 Instead of 2?

This is an important Java detail.

If we write:

5 / 2

Java performs integer division:

2

But we need:

2.5

Therefore, we use:

5 / 2.0

Now Java performs floating-point division:

2.5

That’s why our solution uses:

/ 2.0

instead of:

/ 2

 Comparing All Three Approaches
ApproachTechniqueTime ComplexitySpace Complexity
Brute ForceMerge arraysO(m + n)O(m + n)
BetterTwo pointersO(m + n)O(1)
OptimalBinary SearchO(log(min(m, n)))O(1)
Progression
Brute Force
    ↓
Merge both arrays
    ↓
O(m+n) time
O(m+n) space

        ↓

Better
    ↓
Two pointers
    ↓
O(m+n) time
O(1) space

        ↓

Optimal
    ↓
Binary Search
    ↓
O(log(min(m,n))) time
O(1) space

The Most Important Part to Remember

You do not need to memorize the entire optimal code.

Remember this diagram:

nums1 = [ Aleft | Aright ]

nums2 = [ Bleft | Bright ]

Then remember the correct-partition conditions:

Aleft <= Bright

Bleft <= Aright

And remember:

i + j = half

Therefore:

j = half - i

Once the partition is correct:

Odd
median = max(Aleft, Bleft)
Even
median =
(max(Aleft, Bleft)
 + min(Aright, Bright)) / 2.0
Interview-Friendly Explanation

If an interviewer asks you to explain your solution, you can say:

“Since both arrays are sorted, I don’t need to merge them. I binary-search the smaller array to find a partition such that the combined left half contains half of the elements. If i elements are taken from the first array, then j = half - i elements must be taken from the second array. The partition is valid when Aleft <= Bright and Bleft <= Aright. Once the partition is valid, for an odd total length the median is the maximum element on the left, and for an even total length it is the average of the maximum element on the left and the minimum element on the right.”

This is a very good explanation to use in an interview.

Final Mental Model

Think of the problem as finding this dividing line:

                 LEFT          RIGHT

nums1       [---------- | ----------------]
                         ↑
                         i

nums2       [---------- | ----------------]
                         ↑
                         j

We need exactly half of the elements on the left:

i + j = half

Therefore:

j = half - i

Then check:

Aleft <= Bright
Bleft <= Aright

If the partition is correct, calculate the median.

If the partition is wrong:

Aleft > Bright
        ↓
Move partition LEFT
right = i - 1

or:

Bleft > Aright
        ↓
Move partition RIGHT
left = i + 1

Final Takeaway

The most important lesson from LeetCode #4 is:

Don’t merge the arrays. Find the correct partition.

The progression is:

Brute Force

Merge the arrays:

O(m+n) time
O(m+n) space

Better

Use two pointers:

O(m+n) time
O(1) space

Optimal

Binary search the partition:

O(log(min(m,n))) time
O(1) space

The three formulas worth remembering are:

j = half - i
Aleft <= Bright
Bleft <= Aright

and:

Odd:
max(Aleft, Bleft)

Even:
(max(Aleft, Bleft) + min(Aright, Bright)) / 2.0

Once these three ideas are clear, the entire LeetCode #4 solution becomes much easier to understand and implement.

Leave a Comment

Your email address will not be published. Required fields are marked *

Dare To Dream