4. Median of Two Sorted Arrays

Given two sorted arrays, find the combined median in O(log min(m, n)) time by binary-searching a partition of the shorter array so the left halves of both arrays together equal the right halves — no merging needed.

HardBinary Search on PartitionTwo-Pointer MergeDivide & ConquerTypeScript

PROBLEM What we're solving

Given two sorted arrays nums1 and nums2, return the median of the combined sorted sequence. The required time complexity is O(log(m+n)). Concrete example: nums1=[1,3], nums2=[2,4]. Combined sorted: [1,2,3,4]. Even length, so median = (2+3)/2 = 2.5. Another example: nums1=[1,2], nums2=[3,4,5]. Combined: [1,2,3,4,5], median = 3 (middle element).

KEY IDEA Partition both arrays so left halves ≤ right halves

Insight →The median splits a sequence into two equal halves where every element on the left is ≤ every element on the right. Instead of merging both arrays, binary-search a partition index i in the shorter array. The matching index j = half - i in the longer array is determined automatically. When maxLeft(A) ≤ minRight(B) AND maxLeft(B) ≤ minRight(A), the partition is correct and the median falls out of those four boundary values — no element-by-element scan required.

RECIPE Binary search the partition

  • 0 · Swap if needed. Let A be the shorter array (m ≤ n) so the binary search space is O(log m) — we only ever search A.
  • 1 · Compute half. Set half = ⌊(m+n)/2⌋. This is how many elements belong in the combined left half.
  • 2 · Binary search on i. Try i = (lo+hi)/2 in A; derive j = half - i in B. Left side of both = first i+j elements of the merged sequence.
  • 3 · Read the four boundary values. With ±Infinity sentinels for empty sides: maxLeft1, minRight1 from A; maxLeft2, minRight2 from B.
  • 4 · Check the partition. If maxLeft1 ≤ minRight2 and maxLeft2 ≤ minRight1 → correct partition. Compute median: odd total → min(minRight1, minRight2); even total → (max(maxLeft1,maxLeft2)+min(minRight1,minRight2))/2.
  • 5 · Adjust. If maxLeft1 > minRight2, A's left is too large → move hi = i-1. Otherwise, B's left is too large → move lo = i+1.
Classic confusion → When the total length is odd, the median is a single element — specifically min(minRight1, minRight2), not max(maxLeft1, maxLeft2). The left half holds ⌊(m+n)/2⌋ elements, so the median sits at the front of the right half. Many people reach for the max of the left half first and get confused when the partition sizes are different.

COST Complexity & alternatives

Merge then index
O(m + n)
Linear time; allocates a full merged array.
Binary search partition
O(log min(m,n))
O(1) space; searches only the shorter array.

The naive merge costs O(m+n) time and space — easy to code but violates the problem's O(log(m+n)) requirement. Two-pointer scanning without allocating is still O(m+n). The binary-search partition is the only approach that hits the logarithmic bar.

Pattern transfer →The "binary search on a partition, not on values" trick appears in Kth Smallest in Two Sorted Arrays (generalize half to k), Split Array Largest Sum (binary search on answer), and any problem where you need to find a cut-point that satisfies a two-sided inequality. Once you see maxLeft ≤ minRight as the invariant to enforce, the adjustment rule (move left or right) follows automatically.

RUN IT Binary search the partition; left halves <= right halves

step 0 / 3
STARTBinary search partition of A (len=2). B (len=2). Half=2. We will find i so that left halves of A and B together have 2 elements.
1function findMedianSortedArrays(nums1: number[], nums2: number[]): number {
2 // Binary search on the shorter array for O(log min(m,n))
3 let A = nums1;
4 let B = nums2;
5 if (A.length > B.length) [A, B] = [B, A];
6
7 const m = A.length;
8 const n = B.length;
9 const half = Math.floor((m + n) / 2);
10
11 let lo = 0;
12 let hi = m;
13
14 while (lo <= hi) {
15 const i = Math.floor((lo + hi) / 2); // partition A: A[0..i-1] | A[i..m-1]
16 const j = half - i; // partition B: B[0..j-1] | B[j..n-1]
17
18 const maxLeft1 = i === 0 ? -Infinity : A[i - 1];
19 const minRight1 = i === m ? Infinity : A[i];
20 const maxLeft2 = j === 0 ? -Infinity : B[j - 1];
21 const minRight2 = j === n ? Infinity : B[j];
22
23 if (maxLeft1 <= minRight2 && maxLeft2 <= minRight1) {
24 // Correct partition — left halves contain exactly half elements
25 if ((m + n) % 2 === 1) {
26 return Math.min(minRight1, minRight2);
27 }
28 return (Math.max(maxLeft1, maxLeft2) + Math.min(minRight1, minRight2)) / 2;
29 } else if (maxLeft1 > minRight2) {
30 hi = i - 1; // A's left side too large — shrink it
31 } else {
32 lo = i + 1; // B's left side too large — grow A's left side
33 }
34 }
35
36 throw new Error('Input arrays are not sorted');
37}
A =13
B =24
State
0
lo
2
hi
0
i (A partition)
0
j (B partition)
-inf
maxLeft(A)
+inf
minRight(A)
-inf
maxLeft(B)
+inf
minRight(B)
not started
cmp
maxLeft / boundary (Aleft, Bleft)minRight / boundary (Aright, Bright)median foundpartition moved
slowfast

TYPESCRIPT The solution, annotated

findMedianSortedArrays.ts
function findMedianSortedArrays(nums1: number[], nums2: number[]): number {
  // Binary search on the shorter array for O(log min(m,n))
  let A = nums1;
  let B = nums2;
  if (A.length > B.length) [A, B] = [B, A];

  const m = A.length;
  const n = B.length;
  const half = Math.floor((m + n) / 2);

  let lo = 0;
  let hi = m;

  while (lo <= hi) {
    const i = Math.floor((lo + hi) / 2); // partition A: A[0..i-1] | A[i..m-1]
    const j = half - i;                  // partition B: B[0..j-1] | B[j..n-1]

    const maxLeft1  = i === 0 ? -Infinity : A[i - 1];
    const minRight1 = i === m ?  Infinity : A[i];
    const maxLeft2  = j === 0 ? -Infinity : B[j - 1];
    const minRight2 = j === n ?  Infinity : B[j];

    if (maxLeft1 <= minRight2 && maxLeft2 <= minRight1) {
      // Correct partition — left halves contain exactly half elements
      if ((m + n) % 2 === 1) {
        return Math.min(minRight1, minRight2);
      }
      return (Math.max(maxLeft1, maxLeft2) + Math.min(minRight1, minRight2)) / 2;
    } else if (maxLeft1 > minRight2) {
      hi = i - 1; // A's left side too large — shrink it
    } else {
      lo = i + 1; // B's left side too large — grow A's left side
    }
  }

  throw new Error('Input arrays are not sorted');
}

Reading it block by block

Lines 3–4 — ensure A is shorter. Swapping so A.length ≤ B.length bounds the binary search to O(log m) where m is the smaller size. Without this swap, we might search the longer array unnecessarily.
Lines 7–8 — compute half. half = ⌊(m+n)/2⌋ is how many elements belong in the combined left partition. Because integer division floors, for an odd total the right side has one extra — the median element.
Lines 13–14 — choose partition indices. i cuts A so A[0..i-1] is its left side. j = half - iforces B's partition to make up the rest of the left half. This is the key coupling: adjustingi automatically adjusts j.
Lines 16–19 — four boundary values with ±Infinity sentinels. When i=0, A's left is empty, so maxLeft1 = -Infinity — it trivially satisfies the inequality. When i=m, A's right is empty, so minRight1 = +Infinity. Same logic for B. These sentinels eliminate special cases for empty sides.
Lines 21–26 — correct partition → return median.Both cross-inequalities hold: every element on any left side is ≤ every element on any right side. For odd totals, the median is the smaller of the two right-side minimums. For even totals, average the largest left-side element and the smallest right-side element.
Lines 27–30 — adjust the search window. If maxLeft1 > minRight2, A gave too many large elements to the left — decrease hi. Otherwise B did, so increase lo. Because A and B are sorted and we search A's range, the loop converges in at most O(log m) iterations.
Complexity → O(log min(m, n)) time — we binary search only the shorter array, halving the range each iteration. O(1) extra space — only a handful of scalar variables; no merged array is built.

INTERVIEWFollow-ups they'll ask

  • "What if arrays have duplicates?" The algorithm handles duplicates correctly — the partition condition maxLeft ≤ minRight uses non-strict inequality, so equal boundary values are fine.
  • "Find the k-th smallest element instead of the median?" Replace half = ⌊(m+n)/2⌋ with the target rank k, then use the same partition logic. The median is just the k-th element where k = (m+n)/2.
  • "What if one array is empty?" The sentinels handle it: an empty A gives i=0, maxLeft1=-Infinity, so the condition is trivially satisfied and the answer comes from B alone.
  • "Can you do this with a heap?" A min-heap merge finds the median in O((m+n) log(m+n)) — much worse. The partition binary search is strictly better for two sorted inputs.
  • "What about more than two sorted arrays?" Generalize with a priority-queue merge: merge all k arrays in O(n log k) time. The binary-search partition trick is specific to two arrays.

OPTIMAL Binary Search on Partition

findMedianSortedArrays.ts
function findMedianSortedArrays(nums1: number[], nums2: number[]): number {
  // Binary search on the shorter array for O(log min(m,n))
  let A = nums1;
  let B = nums2;
  if (A.length > B.length) [A, B] = [B, A];

  const m = A.length;
  const n = B.length;
  const half = Math.floor((m + n) / 2);

  let lo = 0;
  let hi = m;

  while (lo <= hi) {
    const i = Math.floor((lo + hi) / 2); // partition A: A[0..i-1] | A[i..m-1]
    const j = half - i;                  // partition B: B[0..j-1] | B[j..n-1]

    const maxLeft1  = i === 0 ? -Infinity : A[i - 1];
    const minRight1 = i === m ?  Infinity : A[i];
    const maxLeft2  = j === 0 ? -Infinity : B[j - 1];
    const minRight2 = j === n ?  Infinity : B[j];

    if (maxLeft1 <= minRight2 && maxLeft2 <= minRight1) {
      // Correct partition — left halves contain exactly half elements
      if ((m + n) % 2 === 1) {
        return Math.min(minRight1, minRight2);
      }
      return (Math.max(maxLeft1, maxLeft2) + Math.min(minRight1, minRight2)) / 2;
    } else if (maxLeft1 > minRight2) {
      hi = i - 1; // A's left side too large — shrink it
    } else {
      lo = i + 1; // B's left side too large — grow A's left side
    }
  }

  throw new Error('Input arrays are not sorted');
}
Complexity → O(log min(m, n)) time — we binary search only the shorter array, halving the range each iteration. O(1) extra space — only a handful of scalar variables; no merged array is built.

ALT 1 Two-pointer merge walk

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

Walk two pointers forward, counting up to the middle index without ever building the merged array — the simple fallback if you blank on the logarithmic partition.

approach-2.ts
function findMedianSortedArrays(nums1: number[], nums2: number[]): number {
  const m = nums1.length;
  const n = nums2.length;
  const total = m + n;

  // We need the elements at positions (total-1)/2 and total/2 of the merged
  // sequence. For odd total these coincide; for even we average them.
  const upper = Math.floor(total / 2);       // index of the second middle element
  const lower = upper - 1 + (total % 2);     // index of the first middle element

  let i = 0; // pointer into nums1
  let j = 0; // pointer into nums2
  let prev = 0; // value at the previous merged position
  let curr = 0; // value at the current merged position

  for (let count = 0; count <= upper; count++) {
    prev = curr;
    if (i < m && (j >= n || nums1[i] <= nums2[j])) {
      curr = nums1[i];
      i++;
    } else {
      curr = nums2[j];
      j++;
    }
  }

  if (lower === upper) {
    return curr; // odd total: the single middle element
  }
  return (prev + curr) / 2; // even total: average the two middles
}
Note → The loop runs exactly upper + 1times, tracking only the two values straddling the median. It is linear, so it fails the problem's O(log(m+n)) requirement — but it is the most natural thing to write under pressure and a perfect correctness baseline.

ALT 2 Binary search for the k-th smallest

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

Discard k/2 elements from one array each step, halving k — a different framing of the logarithmic solution that generalizes cleanly to any rank.

approach-3.ts
function findMedianSortedArrays(nums1: number[], nums2: number[]): number {
  const total = nums1.length + nums2.length;

  // Returns the k-th smallest (1-indexed) across nums1 and nums2, searching
  // within nums1[s1..] and nums2[s2..].
  const kth = (s1: number, s2: number, k: number): number => {
    // If one array is exhausted, the answer is straight from the other.
    if (s1 >= nums1.length) return nums2[s2 + k - 1];
    if (s2 >= nums2.length) return nums1[s1 + k - 1];
    // Base case: the smallest remaining head is the 1st element.
    if (k === 1) return Math.min(nums1[s1], nums2[s2]);

    // Probe the (k/2)-th element of each remaining slice; clamp to array end.
    const half = Math.floor(k / 2);
    const i = Math.min(s1 + half, nums1.length) - 1;
    const j = Math.min(s2 + half, nums2.length) - 1;

    if (nums1[i] <= nums2[j]) {
      // nums1[s1..i] are all too small to be the k-th — discard them.
      return kth(i + 1, s2, k - (i - s1 + 1));
    }
    // Otherwise discard nums2[s2..j].
    return kth(s1, j + 1, k - (j - s2 + 1));
  };

  if (total % 2 === 1) {
    return kth(0, 0, Math.floor(total / 2) + 1);
  }
  const left = kth(0, 0, total / 2);
  const right = kth(0, 0, total / 2 + 1);
  return (left + right) / 2;
}
Note → Each call drops at least ⌊k/2⌋ candidates, so k roughly halves per step and recursion depth is O(log(m+n)). Clamping the probe index to the array end is what keeps it safe when one slice is shorter than k/2.

MNEMONIC The one-liner

"Binary search a cut in A; j fills the rest. When the four borders cross correctly — maxLeft ≤ minRight — you've found the median seam."

TRIGGERS When you see ___ → reach for ___

"median of two sorted arrays"binary search partition on shorter array
"O(log(m+n)) required"partition binary search, not element binary search
need k-th element across two sorted listshalf = k, same partition logic
empty-side edge case in partition±Infinity sentinels for boundary values

SKELETON The reusable shape

skeleton.ts
let A = nums1, B = nums2;
if (A.length > B.length) [A, B] = [B, A];
const m = A.length, n = B.length;
const half = Math.floor((m + n) / 2);
let lo = 0, hi = m;
while (lo <= hi) {
  const i = Math.floor((lo + hi) / 2);
  const j = half - i;
  const maxLeft1  = i === 0 ? -Infinity : A[i - 1];
  const minRight1 = i === m ?  Infinity : A[i];
  const maxLeft2  = j === 0 ? -Infinity : B[j - 1];
  const minRight2 = j === n ?  Infinity : B[j];
  if (maxLeft1 <= minRight2 && maxLeft2 <= minRight1) {
    // found — return median
  } else if (maxLeft1 > minRight2) hi = i - 1;
  else lo = i + 1;
}

FLASHCARDS Tap to flip

What invariant does the correct partition satisfy?
maxLeft(A) ≤ minRight(B) AND maxLeft(B) ≤ minRight(A)— every left-side element is ≤ every right-side element.
tap to flip
1 / 7
Answer to reveal explanations. No penalty for retries.Score: 0 / 9
QUESTION 01
What is the time complexity of the binary-search partition approach?
QUESTION 02
Given nums1=[1,3], nums2=[2,4]. What is the median?
QUESTION 03
When the total number of elements is odd, the median equals:
QUESTION 04
Why do we use ±Infinity as sentinels for boundary values?
QUESTION 05
The condition maxLeft1 > minRight2 tells us to:
QUESTION 06
Why must we ensure nums1 is the shorter array before searching?
QUESTION 07
Trace: nums1=[], nums2=[1,2,3]. What is the median?
QUESTION 08
#4 · Median of Two Sorted ArraysPartition the shorter array so its left half and the complementary slice of the other array together form two equal halves; the four boundary values determine the median in O(log min(m,n)).Which algorithmic approach does this primarily use?
QUESTION 09
#4 · Median of Two Sorted ArraysPartition the shorter array so its left half and the complementary slice of the other array together form two equal halves; the four boundary values determine the median in O(log min(m,n)).Which implementation correctly solves it?