LeetCode 349 - Intersection of Two Arrays
The problem gives us two integer arrays, nums1 and nums2, and asks us to return their intersection. The intersection consists of all values that appear in both arrays. However, the result must contain only unique elements, even if a number appears many times in either array.
Difficulty: 🟢 Easy
Topics: Array, Hash Table, Two Pointers, Binary Search, Sorting
Solution
Problem Understanding
The problem gives us two integer arrays, nums1 and nums2, and asks us to return their intersection. The intersection consists of all values that appear in both arrays. However, the result must contain only unique elements, even if a number appears many times in either array.
For example, if nums1 = [1,2,2,1] and nums2 = [2,2], the number 2 appears in both arrays, so the answer is [2]. Even though 2 appears multiple times, it should only appear once in the result.
The input arrays can contain duplicate values, but duplicates are irrelevant in the final output because uniqueness is required. The order of the output does not matter, which gives us flexibility in how we construct the result.
The constraints are relatively small, with both arrays having at most 1000 elements. This means even slower approaches could technically pass, but the problem is designed to test efficient use of hash tables, sorting, or two pointers.
Several edge cases are important to recognize:
- Arrays may contain many duplicates, so we must avoid returning repeated values.
- One array may contain elements that never appear in the other array.
- The intersection may contain only a single value.
- The arrays may already be unique, or they may contain all identical values.
- Since the values are limited to the range
[0, 1000], there are no concerns about integer overflow or extremely large numbers.
The key requirement is uniqueness, which means we need a reliable way to track whether we have already added a value to the answer.
Approaches
Brute Force Approach
A straightforward solution is to compare every element in nums1 with every element in nums2.
For each number in nums1, we scan through nums2 to see whether the value exists there. If it does, we add it to the result if it has not already been added.
This approach works because every possible pair is checked, guaranteeing that any common element will eventually be found. However, the nested iteration makes the algorithm inefficient.
If n is the length of nums1 and m is the length of nums2, then this approach takes O(n * m) time. While the constraints are small enough for this to pass, it becomes unnecessarily slow for larger inputs.
Optimal Approach
The key observation is that we only need fast membership checking.
A hash set provides average O(1) lookup time. By storing all elements of one array in a set, we can quickly determine whether an element from the other array exists in it.
The algorithm becomes:
- Convert one array into a set.
- Iterate through the second array.
- If a value exists in the set, add it to the result set.
- Convert the result set into a list.
Using sets automatically removes duplicates, which perfectly matches the problem requirement.
This reduces the time complexity to linear time relative to the size of the input arrays.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n * m) | O(k) | Compares every pair of elements, inefficient for large inputs |
| Optimal | O(n + m) | O(n + k) | Uses hash sets for fast lookup and automatic uniqueness |
Here, k represents the number of unique elements in the intersection.
Algorithm Walkthrough
- Create a set from
nums1.
A set allows constant average time membership checking. This means we can quickly determine whether a value from nums2 exists in nums1.
2. Create an empty result set.
Since the problem requires unique intersection values, a set is the ideal structure for storing the answer. Even if we encounter the same value multiple times, it will only appear once in the result set.
3. Iterate through each number in nums2.
For every value, check whether it exists in the set created from nums1.
4. If the value exists in the first set, add it to the result set.
This confirms the value appears in both arrays. 5. Convert the result set into a list and return it.
The problem expects an array-like structure as output, so we convert the set back into a list before returning.
Why it works
The algorithm works because a value belongs in the intersection if and only if it appears in both arrays. The first set records all unique values from nums1. During iteration over nums2, every lookup checks whether the current value also exists in nums1.
Using a result set guarantees uniqueness automatically. Therefore, every returned value appears in both arrays exactly once, which satisfies the problem requirements.
Python Solution
from typing import List
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
nums1_set = set(nums1)
intersection_set = set()
for num in nums2:
if num in nums1_set:
intersection_set.add(num)
return list(intersection_set)
The implementation starts by converting nums1 into a set called nums1_set. This preprocessing step enables fast membership checks.
Next, the code initializes intersection_set, which stores the unique intersection values.
The loop iterates through every value in nums2. For each number, the condition if num in nums1_set checks whether the value exists in the first array. If it does, the value is added to intersection_set.
Finally, the set is converted into a list because LeetCode expects a list return type.
The implementation directly mirrors the algorithm walkthrough and leverages Python's built in set operations for concise and efficient code.
Go Solution
func intersection(nums1 []int, nums2 []int) []int {
nums1Set := make(map[int]bool)
resultSet := make(map[int]bool)
for _, num := range nums1 {
nums1Set[num] = true
}
for _, num := range nums2 {
if nums1Set[num] {
resultSet[num] = true
}
}
result := []int{}
for num := range resultSet {
result = append(result, num)
}
return result
}
Go does not have a built in set type, so maps are commonly used to simulate sets. In this solution, map[int]bool is used to track membership.
The first map stores all values from nums1, while the second map stores the intersection values uniquely.
Unlike Python, Go requires explicit slice creation and appending when constructing the final result. Otherwise, the overall logic remains the same.
There are no concerns about integer overflow because the constraints are small and Go's int type easily handles the input range.
Worked Examples
Example 1
Input:
nums1 = [1,2,2,1]
nums2 = [2,2]
First, create the set from nums1.
nums1_set = {1, 2}
Initialize:
intersection_set = {}
Now iterate through nums2.
| Current Number | Exists in nums1_set? | intersection_set |
|---|---|---|
| 2 | Yes | {2} |
| 2 | Yes | {2} |
The final result is:
[2]
Example 2
Input:
nums1 = [4,9,5]
nums2 = [9,4,9,8,4]
Create the set from nums1.
nums1_set = {4, 5, 9}
Initialize:
intersection_set = {}
Iterate through nums2.
| Current Number | Exists in nums1_set? | intersection_set |
|---|---|---|
| 9 | Yes | {9} |
| 4 | Yes | {9, 4} |
| 9 | Yes | {9, 4} |
| 8 | No | {9, 4} |
| 4 | Yes | {9, 4} |
The final result is:
[9, 4]
The order may vary because sets do not preserve insertion order.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n + m) | Building the first set takes O(n), iterating through the second array takes O(m) |
| Space | O(n + k) | The first set stores unique elements from nums1, the result set stores intersection values |
The time complexity is linear because each element is processed a constant number of times. Set membership checking is average O(1).
The space complexity depends on the number of unique values stored in the sets. In the worst case, all elements are distinct.
Test Cases
sol = Solution()
# Provided example with duplicates
assert sorted(sol.intersection([1,2,2,1], [2,2])) == [2]
# Provided example with multiple intersections
assert sorted(sol.intersection([4,9,5], [9,4,9,8,4])) == [4,9]
# No common elements
assert sorted(sol.intersection([1,2,3], [4,5,6])) == []
# Identical arrays
assert sorted(sol.intersection([1,2,3], [1,2,3])) == [1,2,3]
# One common element repeated many times
assert sorted(sol.intersection([1,1,1,1], [1,1])) == [1]
# Single element arrays with match
assert sorted(sol.intersection([5], [5])) == [5]
# Single element arrays without match
assert sorted(sol.intersection([5], [6])) == []
# One array completely contained in the other
assert sorted(sol.intersection([1,2,3,4], [2,3])) == [2,3]
# Large amount of duplicates
assert sorted(sol.intersection([2]*1000, [2]*1000)) == [2]
# Multiple duplicates with several unique intersections
assert sorted(sol.intersection([1,2,2,3,4], [2,2,4,4,5])) == [2,4]
| Test | Why |
|---|---|
[1,2,2,1] and [2,2] |
Validates duplicate handling |
[4,9,5] and [9,4,9,8,4] |
Validates multiple intersection values |
[1,2,3] and [4,5,6] |
Validates empty intersection |
| Identical arrays | Ensures all unique values are returned |
| Repeated single value | Ensures duplicates appear only once |
| Single element match | Smallest successful case |
| Single element mismatch | Smallest unsuccessful case |
| One array contained in another | Tests subset behavior |
| 1000 repeated values | Stress test for duplicates |
| Multiple duplicate intersections | Validates uniqueness across several values |
Edge Cases
One important edge case occurs when both arrays contain many duplicates of the same value. A naive implementation might accidentally add the same number multiple times to the result. Using a set for the result prevents this issue automatically because sets store only unique elements.
Another edge case happens when the arrays have no common elements. In this scenario, the algorithm should return an empty list. Since values are only added when membership exists in the first set, the result set remains empty and converts cleanly into an empty list.
A third important case is when one array is entirely contained within the other. For example, nums1 = [1,2,3,4] and nums2 = [2,3]. The algorithm correctly identifies both 2 and 3 as common elements because every value in the smaller array is checked independently against the set.
A final edge case involves arrays of length one. Small inputs often expose off by one errors or incorrect initialization. The implementation handles these correctly because the logic does not depend on array size, every element is processed uniformly through the same set lookup mechanism.