Problem overview
The function onlyInFirst gets 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 once, and the values stay in the order they first appear in a. If every value of a is also in b, the result is empty.
For a = [1, 2, 3, 4] and b = [2, 4], the answer is [1, 3]. For a = [5, 6, 5, 7] and b = [7], the answer 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], the result is empty.
Approach
There are two questions to ask about each number in a: is it in b, and is it already in the result? A list answers each by scanning, which gets slow for long lists. A HashSet answers by a fast lookup. So use two sets: one built from b, and one that collects the values already put in the result.
Walking through a from left to right keeps the order. A number goes into the result only if both questions get the answer no.
Step by step
- Build a set from
bwithb.toHashSet(), in avalnamedsetB. Repeats inbcollapse into one, which does not matter here. - Create an empty set
seenwithhashSetOf<Int>(). It remembers what is already in the result, so a repeated value inais not added twice. - Create an empty
mutableListOf<Int>()namedresult. - Loop over
awith the variablenum. - Inside, an
ifwith two checks joined by&&:!setB.contains(num)and!seen.contains(num). Both must be true. - Inside the
if, addnumtoresultand toseen. - After the loop, return
result.
The solution in Kotlin
class Solution {
fun onlyInFirst(a: List<Int>, b: List<Int>): List<Int> {
val setB = b.toHashSet()
val seen = hashSetOf<Int>()
val result = mutableListOf<Int>()
for (num in a) {
if (!setB.contains(num) && !seen.contains(num)) {
result.add(num)
seen.add(num)
}
}
return result
}
}
The && operator stops as soon as its left side is false, so when num is in b the second check is never made. The tests print lists as values separated by spaces. The three examples give:
1 3
5 6
For the third example the result is empty, so nothing is printed for it.
Two more inputs worth trying: a = [-5, 3, -5, 8] with b = [3, 3, 9] gives -5 8 (negative numbers work the same, the repeat in b does no harm, and -5 appears once). And a = [7, 7] with b = [7] gives an empty list, whose size is 0. When no value of a is in b, such as a = [4] with b = [1, 2], the result is 4.
Another way
A library call can express the filter: listOf(5, 6, 5, 7).filter { it !in setOf(7) } gives [5, 6, 5]. It drops the values found in the second collection, but it keeps the repeated 5. This lesson asks for each value once, which is why the extra seen set is there.
Complexity
Building setB takes one step per element of b, and the loop takes one step per element of a, each with fast lookups. The two sets and the result use extra space.
Common mistakes
- Leaving out
seen. The input[5, 6, 5, 7]would then give5 6 5. - Searching
bas a list for every number. It works, but it scansbeach time. - Using
||instead of&&. The number must pass both checks, not one of them. - Returning the set
seen. It does not keep the order ofa.
