Start learning free

LESSON 273 OF 438 · HASH TABLE

Intersection of Two Arrays in Python: O(n + m) with a Dictionary

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 12
    Ctrl + Enter to check

    PYTHON · HASH TABLE

    Intersection Of Two Arrays in Python, explained

    Problem overview

    The method intersection receives two lists of integers, first and second, and returns the numbers that appear in both. Each shared number appears once in the result, however many times it occurs in the inputs, and the result is sorted from smallest to largest. Each list has between 1 and 1000 elements, with values from -1000 to 1000.

    For first = [1, 2, 2, 1] and second = [2, 2] the result is [2]. For first = [4, 9, 5] and second = [9, 4, 9, 8, 4] the result is [4, 9]: both numbers occur in both lists, and although each appears twice in second, it is listed once.

    Approach: remember one list, scan the other

    The brute-force solution checks every element of one list against every element of the other, which costs O(n * m). Checking whether a number is in a list is slow. Checking whether a number is a key of a dictionary is fast, on average O(1), so the idea is to put one list into a dictionary and scan the other list against it.

    The dictionary maps each number of the first list to a counter that starts at 0. The scan through the second list adds one to the counter of every number it finds in the dictionary and ignores numbers that aren't there. When the scan ends, a number whose counter is above 0 occurred in both lists. A dictionary has only one key per number, so duplicates collapse on their own, and the result never repeats a number.

    The code first makes sure the dictionary is built from the shorter list. That keeps the dictionary small, so the extra space is O(min(n, m)). Which list is stored doesn't change the answer, because intersection is symmetric.

    The time limit of O(n + m) excludes the last step. The numbers shared by both lists are collected in the dictionary's order, which is the order they were first stored, and the expected answer is ascending, so the result list is sorted at the end.

    Step by step

    1. Give the lists short names. nums1 and nums2 start out as first and second. They are only new names for the same lists, so the code can swap them without touching the parameters.
    2. Create the dictionary d. Its keys will be numbers from nums1, and its values will count matches in nums2.
    3. Make nums1 the shorter list. If it is longer than nums2, the two names are swapped.
    4. Store the shorter list. The first loop goes through nums1.
    5. Set each counter to 0. A repeated number is simply set to 0 again, so each key exists once.
    6. Scan the longer list with a second loop over nums2.
    7. Skip strangers. A number that isn't a key of d is not in the shorter list, so continue moves on to the next number.
    8. Count matches. For a number that is a key, the else branch adds one to its counter.
    9. Start the result as an empty list.
    10. Walk through the dictionary with d.items(), which gives each key and its counter as a pair, unpacked into k and val.
    11. Keep the matches. If the counter is above 0, the number was found in nums2, so it goes into the result.
    12. Sort and return. result.sort() puts the list in ascending order, and the method returns it.

    The solution in Python

    class Solution:
        def intersection(self, first: List[int], second: List[int]) -> List[int]:
            nums1, nums2 = first, second
            d = {}
            if len(nums1) > len(nums2):
                nums1, nums2 = nums2, nums1
            for i in nums1:
                d[i] = 0
            for i in nums2:
                if i not in d:
                    continue
                else:
                    d[i] = d[i] + 1
            result = []
            for k, val in d.items():
                if val > 0:
                    result.append(k)
            result.sort()
            return result
    

    Python details worth knowing:

    • Dictionaries keep insertion order. d.items() yields the pairs in the order the keys were first stored. If the stored list is [9, 4, 5], that is 9, 4, 5, not ascending, which is why the final sort() is needed.
    • sort() returns None. result.sort() sorts the list in place, so the next line returns result. Writing return result.sort() would return None.
    • in on a dictionary tests keys. i not in d is a hash lookup, not a scan of values.
    • Swapping names. nums1, nums2 = nums2, nums1 exchanges what the two names refer to. No list is copied, and first and second are not changed.
    • Any hashable value works as a key. Integers, including negative ones, are valid dictionary keys.

    Complexity

    Time: O(n + m) plus the sort. Building the dictionary and scanning the other list each take one pass with average O(1) lookups, and reading the dictionary is a pass over at most min(n, m) keys. Sorting k shared numbers costs O(k log k).

    Space: O(min(n, m)). The dictionary holds one key per distinct number of the shorter list, and the result holds at most as many numbers.

    Edge cases and common mistakes

    • No common numbers. For [1] and [2] every counter stays 0, and the result is the empty list [].
    • Duplicates. [1, 2, 2, 1] and [2, 2] give [2]. The counter for 2 reaches 2, but the number is added once.
    • Negative numbers and zero. They work like any other keys, and the sorted result places negatives first.
    • Testing the counter as truthy. A counter of 0 means "not found", so the code compares val > 0. Checking only that the key exists would include every number of the shorter list.
    • Forgetting the sort. The answer may come out in the right numbers but the wrong order, such as [9, 4] instead of [4, 9] for first = [9, 4] and second = [4, 9, 5].

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