Start learning free

LESSON 277 OF 442 · HASH TABLE

Intersection of Two Arrays in Kotlin: O(n + m) with HashSets

Practice O(n + m) Read the full explanation

Given two integer arrays first and second, return an array of their intersection. Each element in the result must be unique, and the result must be sorted in ascending order.

 

Example 1:

Input: first = [1,2,2,1], second = [2,2]
Output: [2]

Example 2:

Input: first = [4,9,5], second = [9,4,9,8,4]
Output: [4,9]
Explanation: 4 and 9 are in both arrays. Each appears twice in second, but only once in the result.

 

Constraints:

  • 1 <= first.length, second.length <= 1000
  • -1000 <= first[i], second[i] <= 1000
Note:

Your algorithm should run in O(n + m) time, plus the time to sort the shared numbers, where n and m are the lengths of first and second.

STEP 1 OF 8
    Ctrl + Enter to check

    KOTLIN · HASH TABLE

    Intersection Of Two Arrays in Kotlin, explained

    Problem overview

    intersection(first, second) takes two IntArray values and returns an IntArray of the numbers that appear in both. Each shared number is listed once, however often it occurs, and the result is sorted in ascending order.

    For first = [1, 2, 2, 1] and second = [2, 2] the answer is [2]. For first = [4, 9, 5] and second = [9, 4, 9, 8, 4] the answer is [4, 9]: both numbers are in both arrays, and although each appears twice in second, the result holds it once. If nothing is shared, the result is an empty array. Each array has between 1 and 1000 elements, and each value is between -1000 and 1000.

    The lesson asks for O(n + m) time, plus the time to sort the shared numbers, where n and m are the array sizes.

    Approach: remember one array in a set

    The obvious solution checks every element of first against every element of second, which takes O(n × m). The question being asked each time is simply "does this number occur in the other array?", and a hash set answers that in constant time on average.

    So the plan has three parts:

    1. Put every number of first into a set called seen. A set stores each value once, so repeats vanish by themselves.
    2. Go through second. Each number that is in seen belongs to both arrays, so add it to a second set, intersectionSet. That set also drops repeats, so a number that shows up twice in second is still stored once.
    3. A hash set has no useful order, but the answer must be ascending. Sort the shared numbers at the end and copy them into an IntArray.

    With first = [4, 9, 5] and second = [9, 4, 9, 8, 4], seen becomes {4, 9, 5}. Walking through second: 9 is in seen, so it is added; 4 is added; the next 9 is already in the result set, so nothing changes; 8 is not in seen; the last 4 changes nothing. The shared set is {9, 4}, and sorting gives [4, 9].

    Only the sort costs more than linear time. If k numbers are shared, it adds O(k log k), which is why the lesson's budget says "plus the time to sort the shared numbers".

    Step by step

    1. Give the inputs shorter names. nums1 and nums2 are val aliases of first and second. No array is copied: both names refer to the same array.
    2. Create the first set. A HashSet<Int> named seen will hold the numbers of nums1.
    3. Loop over nums1. Every number must be in seen before nums2 is examined.
    4. Add each number. Adding a value that is already present changes nothing.
    5. Create the result set. A second HashSet<Int>, intersectionSet, collects the shared numbers.
    6. Loop over nums2. Each of its numbers is tested in turn.
    7. Test membership. If seen.contains(i), the number is in both arrays, so add it to intersectionSet.
    8. Return it sorted. sorted() puts the numbers in ascending order, and toIntArray() converts the result to the IntArray the function must return.

    The solution in Kotlin

    class Solution {
        fun intersection(first: IntArray, second: IntArray): IntArray {
            val nums1 = first
            val nums2 = second
            val seen = HashSet<Int>()
            for (i in nums1) {
                seen.add(i)
            }
            val intersectionSet = HashSet<Int>()
            for (i in nums2) {
                if (seen.contains(i)) {
                    intersectionSet.add(i)
                }
            }
            return intersectionSet.sorted().toIntArray()
        }
    }
    

    Kotlin details worth a look:

    • IntArray, sets and boxing. An IntArray holds primitive ints, while a HashSet<Int> stores boxed Integer objects. Kotlin converts between them automatically in the loops, and toIntArray() converts the sorted list back.
    • sorted() returns a new List<Int>. It doesn't change the set, which couldn't be reordered anyway. toIntArray() then builds an array from that list.
    • add returns a Boolean: true if the value was new and false if the set already held it. The solution ignores that result, which is fine.
    • Printing an array. println(result) on an IntArray doesn't show the numbers. It prints a type tag and a hash code, in the form [I@ followed by hexadecimal digits. To see the contents, use result.contentToString(), which gives [4, 9].
    • for (i in nums1) loops over the elements directly, with no index.

    Complexity

    Time: O(n + m) plus O(k log k). Building seen takes n additions and scanning second takes m lookups, each constant time on average. Sorting the k shared numbers adds k log k, where k is at most the smaller array's size.

    Space: O(n + k). seen holds up to n distinct values, and intersectionSet, the sorted list and the result array each hold at most k values.

    Edge cases and common mistakes

    • No shared numbers. [1, 2] and [3, 4] give an empty array, printed as [].
    • Repeated numbers. The result must not contain duplicates. Collecting matches in a list instead of intersectionSet would return [2, 2] for the first example.
    • Negative values. Sorting handles them: [-1, -3, 5] and [5, -3, -1] give [-3, -1, 5].
    • Skipping the sort. A HashSet doesn't promise any order, so the result must be sorted explicitly, or the output may not match the expected ascending order.
    • Checking against the wrong array. The set must hold one array completely before the other is scanned. Checking while still filling it misses numbers that appear later in first.

    Testing

    • first = [1, 2, 2, 1], second = [2, 2] gives [2].
    • first = [4, 9, 5], second = [9, 4, 9, 8, 4] gives [4, 9].
    • first = [1, 2], second = [3, 4] gives [].
    • first = [-1, -3, 5], second = [5, -3, -1] gives [-3, -1, 5].
    Keep going with KotlinAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free