Array · 16 of 36
Find common elements In 3 sorted arrays
Find the numbers that appear in all three sorted lists by walking one finger along each list.
- Simple way
- time O(n1 * (log n2 + log n3))
room O(1) - Clever way
- time O(n1 + n2 + n3)
room O(1)
The problem, simply
You get three arrays. Each one is sorted from small to big. Find every number that is in all three arrays, and give them back in sorted order, each number once.
A story to remember it
Picture it
Three sorted lists. Only 20 and 80 are in all three.
- answer
Examples
A normal case
- In
A = [1, 5, 10, 20, 40, 80], B = [6, 7, 20, 80, 100], C = [3, 4, 15, 20, 30, 70, 80, 120]- Out
[20, 80]
20 and 80 are the only numbers in all three.
Repeats inside a list
- In
A = [1, 1, 2, 2, 5], B = [1, 1, 2, 5], C = [1, 2, 2, 5, 5]- Out
[1, 2, 5]
Each shared number is written once, even though it repeats. The code checks the last number written before adding.
Nothing shared
- In
A = [1, 2], B = [3, 4], C = [5, 6]- Out
[]
The fingers keep moving until list A runs out.
An empty list
- In
A = [], B = [1], C = [1]- Out
[]
If one list is empty, nothing can be in all three.
Way 1: the simple way
For each number in A, look it up in B and in C. Because B and C are sorted, a lookup can use binary search: open the list in the middle, and throw away the half that is too big or too small.
- For each number
xin A (skip repeats). - Binary search for
xin B and in C. - If both find it, add
x.
class Solution {
public List<Integer> commonElements(int[] a, int[] b, int[] c) {
List<Integer> out = new ArrayList<>();
for (int i = 0; i < a.length; i++) {
if (i > 0 && a[i] == a[i - 1]) continue; // skip repeats in A
if (Arrays.binarySearch(b, a[i]) >= 0 && Arrays.binarySearch(c, a[i]) >= 0) {
out.add(a[i]);
}
}
return out;
}
}How long it takes: for each of the n1 numbers in A, two binary searches, so O(n1 * (log n2 + log n3)). Extra room: O(1) besides the answer. This is actually a good choice when A is tiny and B and C are huge.
Way 2: the clever way
Put one finger on each list: i on A, j on B, k on C.
- Look at the three numbers under the fingers.
- If all three are equal, it is shared. Write it down (unless it was the last thing written) and move all three fingers.
- Otherwise, find the smallest of the three and move every finger that points at it. That number can never be shared.
- Stop as soon as any finger falls off the end of its list.
Three fingers, always move the smallest
01 / 14
Fingers show 1, 6 and 3. The smallest is 1. It cannot be in all three lists, because the other fingers only ever move to bigger numbers. So move i.
- smallest
- 1
- common
- []
- checking
- answer
- rejected
class Solution {
public List<Integer> commonElements(int[] a, int[] b, int[] c) {
List<Integer> out = new ArrayList<>();
int i = 0, j = 0, k = 0;
while (i < a.length && j < b.length && k < c.length) {
int small = Math.min(a[i], Math.min(b[j], c[k]));
if (a[i] == small && b[j] == small && c[k] == small) {
if (out.isEmpty() || out.get(out.size() - 1) != small) out.add(small);
i++; j++; k++; // shared: move everyone
} else {
if (a[i] == small) i++; // the smallest can never be shared
if (b[j] == small) j++;
if (c[k] == small) k++;
}
}
return out;
}
}How long it takes: every step moves at least one finger forward, and no finger moves back, so at most n1 + n2 + n3 steps, O(n1 + n2 + n3). Extra room: three fingers, O(1).
Watch out for
- Stop when any list ends, not when all end.
- Moving a finger that is not on the smallest number skips a possible match.
- Repeated numbers: without the "last written" check,
[2, 2]in all three lists gives[2, 2].
What interviewers ask next
- What about
ksorted lists? Intersect them two at a time, smallest lists first, or keep a finger per list and always move the smallest one using a min heap.O(N log k)forNtotal numbers. - What if the lists are not sorted? Count with a hash map: add each list's distinct numbers and keep those with count 3.
O(n1 + n2 + n3)time, butO(n)extra room. - What if one list is tiny and the others are huge? Use Way 1, binary searching the big lists, or gallop: jump 1, 2, 4, 8 places ahead to find the next candidate quickly.
- Keep repeats, like a multiset (
[2, 2]shared twice)? Drop the "last written" check.
Remember this
Grown-up words: the fingers are pointers (three pointers), looking in the middle of a sorted list is binary search, and the shared numbers are the intersection of the three sets.