Skip to content
dsa450for five year olds

Searching & Sorting · 23 of 36

K-th Element of Two Sorted Arrays

Find the k-th smallest number of two sorted lists put together, without actually putting them together.

Simple way
time O(k)
room O(1)
Clever way
time O(log(min(n, m)))
room O(1)

The problem, simply

You get two arrays, each sorted from small to big, of sizes n and m, and a number k. Imagine pouring both into one sorted array. Return the number at position k (counting from 1).

You should not build the big array. The fast answer looks at only a few numbers.

A story to remember it

Picture it

arraytop array
012341581012cut1
arraybottom array
0123452346911cut2

Take 1 from the top and 3 from the bottom: 1, 2, 3, 4. Everything taken is at most everything left behind, so these are the 4 smallest, and the 4th smallest is the biggest taken number, 4.

  • answer
  • path
  • ignored

Examples

A normal case

In
a = [2, 3, 6, 7, 9], b = [1, 4, 8, 10], k = 5
Out
6

Together: 1, 2, 3, 4, 6, 7, 8, 9, 10. The 5th is 6.

Bigger arrays

In
a = [100, 112, 256, 349, 770], b = [72, 86, 113, 119, 265, 445, 892], k = 7
Out
256

Together the first seven are 72, 86, 100, 112, 113, 119, 256.

The walkthrough case

In
a = [1, 5, 8, 10, 12], b = [2, 3, 4, 6, 9, 11], k = 4
Out
4

Three of the four smallest come from b.

k is 1

In
a = [3], b = [1, 2], k = 1
Out
1

The smallest of the two first numbers.

One array is empty

In
a = [], b = [4, 5, 6], k = 2
Out
5

Everything comes from b.

Way 1: the simple way

Do the merge step of merge sort, but stop as soon as you have taken k numbers. Two fingers start at the front of each array. Each step, take the smaller front number and move that finger.

java
class Solution {
    public int kthElement(int[] a, int[] b, int k) {
        int i = 0, j = 0, last = -1;
        for (int taken = 0; taken < k; taken++) {
            if (j == b.length || (i < a.length && a[i] <= b[j])) {
                last = a[i++];          // a's front is smaller (or b is used up)
            } else {
                last = b[j++];
            }
        }
        return last;                    // the k-th number taken
    }
}

How long it takes: k steps, O(k), which can be n + m. Extra room: O(1).

Way 2: the clever way

The k smallest numbers are always a front part of a plus a front part of b. Say we take c1 from a and c2 = k - c1 from b. Binary search for the right c1, on the shorter array.

  1. Make a the shorter array. c1 can be from max(0, k - m) to min(k, n).
  2. Guess c1 in the middle; c2 = k - c1.
  3. Look at the border: l1 = a[c1 - 1] and l2 = b[c2 - 1] are the last ones taken; r1 = a[c1] and r2 = b[c2] are the first ones left. Use minus and plus infinity when a side is empty.
  4. If l1 <= r2 and l2 <= r1, the split is right. The answer is max(l1, l2).
  5. If l1 > r2, we took too many from a: hi = c1 - 1.
  6. Otherwise we took too few from a: lo = c1 + 1.

The bottom row in the animation lists every possible c1.

Binary search for how many to take from the top array

01 / 07

arraytop array
012341581012
arraybottom array
0123452346911
arrayhow many to take from the top array
012345still possible012345hilo

We want the 4th smallest of both arrays together. Take some from the front of the top array and the rest from the front of the bottom array, 4 in total. Binary search how many come from the top: anywhere from 0 to 4.

k
4
  • looking now
  • checking
  • answer
  • rejected
  • path
  • ignored
java
class Solution {
    public int kthElement(int[] a, int[] b, int k) {
        if (a.length > b.length) return kthElement(b, a, k);   // search the shorter one
        int n = a.length, m = b.length;
        int lo = Math.max(0, k - m), hi = Math.min(k, n);
        while (lo <= hi) {
            int c1 = lo + (hi - lo) / 2;         // how many from a
            int c2 = k - c1;                     // the rest from b
            int l1 = c1 == 0 ? Integer.MIN_VALUE : a[c1 - 1];
            int l2 = c2 == 0 ? Integer.MIN_VALUE : b[c2 - 1];
            int r1 = c1 == n ? Integer.MAX_VALUE : a[c1];
            int r2 = c2 == m ? Integer.MAX_VALUE : b[c2];
            if (l1 <= r2 && l2 <= r1) return Math.max(l1, l2);   // a clean border
            if (l1 > r2) hi = c1 - 1;            // too many from a
            else lo = c1 + 1;                    // too few from a
        }
        return -1;                               // only if k is out of range
    }
}

How long it takes: the range of c1 has at most min(n, m) + 1 choices and halves each time, O(log(min(n, m))). Extra room: O(1).

Watch out for

  • Forgetting the empty sides. When c1 == 0, nothing was taken from a, so l1 must not block anything: use the smallest possible value. When c1 == n, nothing is left in a: use the biggest possible value.
  • Searching on the longer array. It still works if the range is right, but it is slower and the range math is easier to get wrong.
  • k counts from 1 here. If your judge counts from 0, add 1 first.

What interviewers ask next

  • Median of two sorted arrays (LeetCode 4)? It is this problem with k = (n + m + 1) / 2, plus the next number when n + m is even, which is min(r1, r2) at the same split.
  • Another O(log k) way? Compare a[k/2 - 1] and b[k/2 - 1]. The smaller side's first k/2 numbers cannot hold the answer, so drop them and look for the (k - k/2)-th in what is left.
  • What about the k-th smallest in a sorted matrix, or among many sorted lists? Use a min heap of fronts (O(k log lists)), or binary search on the value and count how many numbers are at most it.
  • Why is max(l1, l2) the answer? The taken part holds exactly k numbers, all at most everything left. So the biggest taken number is in position k.

Remember this

Grown-up words: the guess is a partition (or cut) of each array, the border check is the partition condition, minus and plus infinity are sentinels, and the median is the special case k = (n + m + 1) / 2.