Array · 19 of 36
Find whether an array is a subset of another array
Check whether every number of a small list can be found in a big list, using each copy in the big list only once.
- Simple way
- time O(m * n)
room O(m) - Clever way
- time O(m + n)
room O(m)
The problem, simply
You get a big array a (length m) and a small array b (length n). Is b a subset of a? That means every number in b can be matched to its own copy in a.
Repeats matter. If b has three 3s, then a needs at least three 3s.
A story to remember it
Picture it
The big array has two 3s. The small array asks for three 3s, so it is not a subset, even though every number in it appears in the big array.
- checking
- rejected
Examples
A normal case
- In
a = [11, 1, 13, 21, 3, 7], b = [11, 3, 7, 1]- Out
true
Each number of b has its own copy in a.
Not enough copies
- In
a = [11, 1, 13, 21, 3, 7, 3], b = [3, 7, 1, 3, 3]- Out
false
a has two 3s, b needs three. This is the animated example.
A missing number
- In
a = [1, 2, 3, 4, 5, 6], b = [1, 2, 4, 9]- Out
false
9 is not in a at all.
Empty small array
- In
a = [4, 5], b = []- Out
true
An empty wish list is always possible.
Way 1: the simple way
For each number in b, search a for a copy that is not used yet, and mark it used.
- Make
used, a true or false mark for each box ofa. - For each
xinb, walkalooking for an unused box equal tox. Mark it used. If none is found, return false.
class Solution {
public boolean isSubset(int[] a, int[] b) {
if (b.length > a.length) return false;
boolean[] used = new boolean[a.length];
for (int x : b) {
boolean found = false;
for (int i = 0; i < a.length; i++) {
if (!used[i] && a[i] == x) { // an unused copy of x
used[i] = true;
found = true;
break;
}
}
if (!found) return false;
}
return true;
}
}How long it takes: for each of the n numbers in b we may walk all m boxes, O(m * n). Extra room: the marks, O(m).
Way 2: the clever way
Count first, then spend the counts.
- Walk
aonce and count each number in a hash map. - Walk
b. For eachx, if its count is 0 (or it is missing), return false. Otherwise lower the count by 1. - If you reach the end of
b, return true.
Count the big array, then spend the counts
01 / 06
Count every number of the big array once. 3 shows up twice, every other number once.
- looking now
- checking
- done
- rejected
- just added
class Solution {
public boolean isSubset(int[] a, int[] b) {
if (b.length > a.length) return false;
Map<Integer, Integer> count = new HashMap<>();
for (int x : a) count.merge(x, 1, Integer::sum); // piles of stickers
for (int x : b) {
int c = count.getOrDefault(x, 0);
if (c == 0) return false; // pile is empty
count.put(x, c - 1); // take one
}
return true;
}
}How long it takes: one walk over a and one over b, O(m + n) on average. Extra room: the map holds at most m different numbers, O(m).
Watch out for
- If
bis longer thana, the answer is false right away. - Build the counts from the big array, not the small one. Counting
band checkingaanswers a different question. - An empty
bis a subset of anything, including an emptya.
What interviewers ask next
- Both arrays are sorted? Walk them with two fingers: if
a[i] < b[j]movei, if equal move both, ifa[i] > b[j]thenb[j]is missing, return false.O(m + n)time andO(1)room. - Not sorted and no extra room allowed? Sort both, then use the two fingers:
O(m log m + n log n). - Can the small array be built from the letters of a magazine (ransom note)? Same counting, with 26 letter counts instead of a map.
- Is
ba subsequence ofa(same order, gaps allowed)? Different problem: one finger on each, and move thebfinger only on a match.
Remember this
Grown-up words: the piles are a frequency map (a hash map from value to count), and "each copy used once" means we are checking a multiset subset.