Start learning free

LESSON 277 OF 442 · HASH TABLE

Intersection of Two Arrays in Scala: O(n + m) with Mutable Sets

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

    SCALA · HASH TABLE

    Intersection Of Two Arrays in Scala, explained

    Problem overview

    The method intersection(first: Array[Int], second: Array[Int]): Array[Int] returns the numbers that appear in both arrays. Each number is listed once, however often it occurs, and the result is sorted in ascending order. Each array has between 1 and 1000 elements, with values from -1000 to 1000.

    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 the repeats in second add nothing.

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

    Approach: remember one array in a set

    The simple way to find shared numbers is to take each number of first and scan second for it. That costs O(n × m). The question being asked over and over is "does this number occur in that array?", and a hash set answers it in O(1) on average.

    So the plan has two passes:

    1. Put every number of first into a set called seen. A set stores each value once, so repeats vanish.
    2. Walk through second. For each number that is in seen, add it to a second set, intersectionSet.

    A number reaches intersectionSet only if it was in first (it is in seen) and in second (the loop is looking at it). Since this is also a set, a shared number that appears many times in second is stored once, which is what the problem wants.

    The last step is the order. A hash set has no useful order, so the shared numbers are copied into an array and sorted. With first = [4, 9, 5] and second = [9, 4, 9, 8, 4]: seen becomes {4, 9, 5}, the second pass finds 9, 4, 9 and 4 in it, and intersectionSet is {9, 4}. Sorting gives [4, 9].

    The two passes cost O(n) and O(m). Sorting k shared numbers costs O(k log k), which is the "plus the time to sort" in the lesson's note.

    Step by step

    1. Give the inputs shorter names. nums1 and nums2 are just aliases for first and second. No numbers are copied.
    2. Create the first set. seen is an empty scala.collection.mutable.Set[Int]. It will hold the numbers of nums1.
    3. Loop over nums1. Every number of the first array must be in seen before the second array is checked.
    4. Add each number. seen += i inserts it. Adding a number that is already there changes nothing.
    5. Create the result set. intersectionSet is a second empty mutable set. It will hold the shared numbers once each.
    6. Loop over nums2. Each number is tested against seen, one at a time.
    7. Keep the shared ones. If seen.contains(i), add i to intersectionSet.
    8. Return a sorted array. toArray copies the set into an Array[Int], and sorted puts it in ascending order.

    The solution in Scala

    class Solution {
        def intersection(first: Array[Int], second: Array[Int]): Array[Int] = {
            val nums1 = first
            val nums2 = second
            var seen = scala.collection.mutable.Set[Int]()
            for (i <- nums1) {
                seen += i
            }
            var intersectionSet = scala.collection.mutable.Set[Int]()
            for (i <- nums2) {
                if (seen.contains(i)) {
                    intersectionSet += i
                }
            }
            return intersectionSet.toArray.sorted
        }
    }
    

    The Scala details worth a closer look:

    • Mutable versus immutable sets. A plain Set in Scala is immutable, and += on a var holding one would build a new set each time and reassign the variable. The code uses scala.collection.mutable.Set, which changes in place. Because the sets are mutable, the var declarations are not strictly needed: a val would work equally well, since only the contents change.
    • += on a mutable set. seen += i adds the element to the same set object. Adding an existing element is allowed and does nothing.
    • contains. seen.contains(i) returns a Boolean and is O(1) on average for a hash set. The shorter form seen(i) means the same.
    • toArray.sorted. toArray on a mutable.Set[Int] gives an Array[Int], and sorted returns a new sorted array, using the natural order of Int. The set's own order is unspecified, so never rely on it.
    • Looping over an array. for (i <- nums1) visits each element in order, with i an Int. The names i here are values, not indices.
    • The last expression. return intersectionSet.toArray.sorted is outside of any loop, so the explicit return is an ordinary one.

    Complexity

    Time: O(n + m) plus the sort. The first loop does n insertions and the second does m lookups and at most m insertions, each O(1) on average. Sorting the k shared numbers adds O(k log k), where k can't exceed the smaller of the two lengths.

    Space: O(n + k). seen holds up to n numbers, the result set holds up to k, and the returned array has k elements. With values limited to -1000 to 1000, each set has at most 2001 entries.

    Edge cases and common mistakes

    • No shared numbers. [1, 2] and [3, 4] give an empty array, []. The loops run normally and the result set stays empty.
    • Repeats. [1, 2, 2, 1] and [2, 2] give [2], not [2, 2]. Using a list or an array for the result instead of a set would keep the duplicates.
    • Negative numbers. [-1, 0, 5] and [5, -1, -1] give [-1, 5]. Sorting orders them numerically, so the negative one comes first.
    • Forgetting to sort. The order of a hash set is unspecified, so without sorted the result can come out in any order, and the lesson's tests expect ascending order.
    • Checking against the wrong set. The second loop tests seen, the numbers of first. Testing against intersectionSet would always fail, because it starts empty.
    • Using an immutable set. With a plain Set[Int](), the call seen += i only compiles when seen is a var, and each addition creates a new set. The mutable set is the right tool for this loop.

    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, 0, 5], second = [5, -1, -1] gives [-1, 5].
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free