Problem overview
The method onlyInFirst receives two lists of integers, a and b. It returns a new list with the values of a that are not in b. Each value goes into the result only once, and the values keep the order in which they first appear in a. If every value of a is also in b, the result is an empty list.
For a = [1, 2, 3, 4] and b = [2, 4] the result is [1, 3]. For a = [5, 6, 5, 7] and b = [7] the result is [5, 6]: 7 is in b, and 5 appears twice in a but only once in the result. For a = [1, 2] and b = [2, 3, 1] nothing is left, so the result is [].
Approach
There are two questions for each number of a: is it in b, and is it already in the result? Both are "is this value in a collection" questions, and a HashSet answers those quickly. Putting the values of b in a set makes the first question cheap. A second set, seen, remembers which values were already put in the result, which handles the repeats inside a. A list still holds the result, because it keeps the order.
Step by step
- Create
setB, aHashSet<Integer>built fromb. Repeats inbcollapse into one, which is fine for a membership check. - Create an empty
HashSet<Integer>namedseen. - Create an empty
List<Integer>namedresult. - Loop over
afrom left to right, with anintnamednum. Reading in order keeps the order of first appearance. - Inside the loop, check that
numis not insetBand not inseen, joined with&&. Both must be true. - Inside the
if, addnumtoresult. - Also add
numtoseen, so a later copy is skipped. - After the loop, return
result. It may be empty.
The solution in Java
class Solution {
public List<Integer> onlyInFirst(List<Integer> a, List<Integer> b) {
HashSet<Integer> setB = new HashSet<>(b);
HashSet<Integer> seen = new HashSet<>();
List<Integer> result = new ArrayList<>();
for (int num : a) {
if (!setB.contains(num) && !seen.contains(num)) {
result.add(num);
seen.add(num);
}
}
return result;
}
}
The lists hold Integer objects, and the loop variable is an int. Java converts between them for you. In the condition, && stops as soon as the left side is false, so a value that is in b is rejected without looking at seen.
Tracing the second example: 5 is not in setB (which holds only 7) and not in seen, so it goes in the result. 6 does the same. The second 5 is in seen and is skipped. 7 is in setB and is skipped. The result is [5, 6].
The first example runs without the second set doing any work. 1 and 3 are not in setB, which holds 2 and 4, so they go in the result in that order. 2 and 4 are in setB, so they are skipped. The third example shows the empty case: both 1 and 2 are in setB, so nothing is added and the method returns an empty list.
Edge cases and common mistakes
- Skipping
seen. Then the second 5 would be added again and the result would be[5, 6, 5]. - Using
b.containsdirectly. A list'scontainsscans its values one by one. BuildingsetBonce is faster for long lists. - Using
||instead of&&. A number would be kept when only one of the two checks passes. - Removing from
a. The method should build a new list and leave its inputs alone.
Testing
Running the solution on the three examples gives:
[1, 3]
[5, 6]
[]
