350. Intersection of Two Arrays II
Problem Description
You are given two integer arrays nums1
and nums2
. Your task is to find the intersection of these two arrays and return it as a new array.
The intersection should include all elements that appear in both arrays, and each element should appear in the result as many times as it appears in both arrays (taking the minimum count from each array). For example, if a number appears 3 times in nums1
and 2 times in nums2
, it should appear 2 times in the result.
The elements in the result array can be returned in any order.
Example scenarios:
- If
nums1 = [1,2,2,1]
andnums2 = [2,2]
, the intersection would be[2,2]
because the number 2 appears twice in both arrays. - If
nums1 = [4,9,5]
andnums2 = [9,4,9,8,4]
, the intersection would be[4,9]
or[9,4]
(order doesn't matter) because 4 appears once innums1
and twice innums2
(so we take 1), and 9 appears once innums1
and twice innums2
(so we take 1).
The solution uses a hash table to count occurrences of elements in nums1
. Then it iterates through nums2
, and for each element that exists in the hash table with a count greater than 0, it adds that element to the result and decrements its count in the hash table. This ensures each common element is included the correct number of times in the intersection.
Intuition
The key insight is that we need to track how many times each element appears in both arrays and include the minimum count in our result.
Think of it like comparing two shopping lists. If your first list has 3 apples and your second list has 2 apples, the common items between both lists would be 2 apples (the smaller count).
To efficiently track and compare these counts, we can use a hash table (Counter in Python) to store the frequency of each element from the first array. This gives us O(1)
lookup time for checking if an element exists and what its count is.
As we iterate through the second array, for each element we encounter:
- If it exists in our hash table (meaning it was in the first array) and still has a positive count, it's part of the intersection
- We add it to our result and decrement its count in the hash table
- Decrementing ensures we don't include more occurrences than what exists in the first array
This approach is more efficient than sorting both arrays or using nested loops. By using a hash table, we avoid repeatedly searching through nums1
for each element in nums2
. The decrementing mechanism naturally handles the "minimum count" requirement - once we've used up all occurrences from nums1
, that element won't be added to the result anymore, even if it appears more times in nums2
.
Solution Approach
The implementation uses a hash table to efficiently find the intersection of two arrays with duplicates.
Step 1: Count elements in the first array
cnt = Counter(nums1)
We use Python's Counter
class (a specialized dictionary) to count the frequency of each element in nums1
. For example, if nums1 = [1,2,2,1]
, then cnt
becomes {1: 2, 2: 2}
.
Step 2: Initialize the result array
ans = []
Create an empty list to store the intersection elements.
Step 3: Iterate through the second array
for x in nums2: if cnt[x]: ans.append(x) cnt[x] -= 1
For each element x
in nums2
:
- Check if
cnt[x]
is greater than 0 (meaningx
exists innums1
and hasn't been fully used) - If true, add
x
to the answer array - Decrement
cnt[x]
by 1 to mark that we've used one occurrence of this element
The condition if cnt[x]
works because:
- If
x
exists in the counter with a positive value, it evaluates toTrue
- If
x
doesn't exist or has value 0, it evaluates toFalse
Step 4: Return the result
return ans
Time Complexity: O(m + n)
where m
and n
are the lengths of nums1
and nums2
respectively. We traverse each array once.
Space Complexity: O(min(m, n))
for the hash table, as it stores at most the unique elements from the smaller array.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a concrete example where nums1 = [1,2,2,1]
and nums2 = [2,2,3]
.
Step 1: Build frequency counter for nums1
- Process
nums1 = [1,2,2,1]
- Counter becomes:
{1: 2, 2: 2}
- This tells us that 1 appears twice and 2 appears twice in nums1
Step 2: Process nums2 element by element
Processing first element (2):
- Check: Is
cnt[2] > 0
? Yes,cnt[2] = 2
- Add 2 to result:
ans = [2]
- Decrement counter:
cnt[2] = 2 - 1 = 1
- Counter now:
{1: 2, 2: 1}
Processing second element (2):
- Check: Is
cnt[2] > 0
? Yes,cnt[2] = 1
- Add 2 to result:
ans = [2, 2]
- Decrement counter:
cnt[2] = 1 - 1 = 0
- Counter now:
{1: 2, 2: 0}
Processing third element (3):
- Check: Is
cnt[3] > 0
? No,cnt[3] = 0
(doesn't exist) - Skip this element
- Result remains:
ans = [2, 2]
Step 3: Return result
- Final answer:
[2, 2]
The algorithm correctly identifies that 2 appears twice in both arrays (twice in nums1, twice in nums2), so it includes 2 twice in the intersection. The element 1 from nums1 and element 3 from nums2 don't appear in both arrays, so they're excluded from the result.
Solution Implementation
1class Solution:
2 def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
3 """
4 Find the intersection of two arrays, including duplicates.
5 Each element in the result appears as many times as it shows
6 in both arrays.
7
8 Args:
9 nums1: First integer array
10 nums2: Second integer array
11
12 Returns:
13 List containing the intersection of both arrays
14 """
15 # Count frequency of each element in nums1
16 frequency_map = Counter(nums1)
17
18 # Store the intersection result
19 result = []
20
21 # Iterate through nums2 to find common elements
22 for num in nums2:
23 # If current number exists in frequency map with count > 0
24 if frequency_map[num] > 0:
25 # Add to result
26 result.append(num)
27 # Decrement the count to handle duplicates correctly
28 frequency_map[num] -= 1
29
30 return result
31
1class Solution {
2 public int[] intersect(int[] nums1, int[] nums2) {
3 // Create a frequency array to count occurrences of each element in nums1
4 // Array size 1001 assumes elements range from 0 to 1000
5 int[] frequencyCount = new int[1001];
6
7 // Count the frequency of each element in nums1
8 for (int element : nums1) {
9 frequencyCount[element]++;
10 }
11
12 // List to store the intersection result
13 List<Integer> intersectionResult = new ArrayList<>();
14
15 // Iterate through nums2 and check if element exists in frequencyCount
16 for (int element : nums2) {
17 // If the element has remaining count in frequencyCount,
18 // add it to result and decrement the count
19 if (frequencyCount[element] > 0) {
20 frequencyCount[element]--;
21 intersectionResult.add(element);
22 }
23 }
24
25 // Convert the ArrayList to int array and return
26 return intersectionResult.stream()
27 .mapToInt(Integer::intValue)
28 .toArray();
29 }
30}
31
1class Solution {
2public:
3 vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
4 // Hash map to store the frequency of each element in nums1
5 unordered_map<int, int> frequencyMap;
6
7 // Count occurrences of each element in the first array
8 for (int element : nums1) {
9 ++frequencyMap[element];
10 }
11
12 // Vector to store the intersection result
13 vector<int> result;
14
15 // Iterate through the second array to find common elements
16 for (int element : nums2) {
17 // Check if the element exists in the frequency map with count > 0
18 // Decrement the count after checking (post-decrement)
19 if (frequencyMap[element]-- > 0) {
20 // Add the element to the result as it's part of the intersection
21 result.push_back(element);
22 }
23 }
24
25 return result;
26 }
27};
28
1/**
2 * Finds the intersection of two arrays, including duplicates.
3 * Each element in the result appears as many times as it shows up in both arrays.
4 * @param nums1 - First input array
5 * @param nums2 - Second input array
6 * @returns Array containing the intersection of nums1 and nums2
7 */
8function intersect(nums1: number[], nums2: number[]): number[] {
9 // Create a frequency map to count occurrences of each number in nums1
10 const frequencyMap: Record<number, number> = {};
11
12 // Count the frequency of each number in the first array
13 for (const num of nums1) {
14 frequencyMap[num] = (frequencyMap[num] || 0) + 1;
15 }
16
17 // Array to store the intersection result
18 const result: number[] = [];
19
20 // Iterate through the second array and check for common elements
21 for (const num of nums2) {
22 // If the current number exists in the frequency map with count > 0
23 if (frequencyMap[num] > 0) {
24 // Add it to the result and decrement its count
25 result.push(num);
26 frequencyMap[num]--;
27 }
28 }
29
30 return result;
31}
32
Time and Space Complexity
The time complexity is O(m + n)
, where m
is the length of nums1
and n
is the length of nums2
. This is because:
- Creating the Counter from
nums1
takesO(m)
time - Iterating through each element in
nums2
takesO(n)
time - Each dictionary lookup and update operation inside the loop takes
O(1)
time on average - Therefore, the total time complexity is
O(m) + O(n) = O(m + n)
The space complexity is O(m)
, where m
is the length of nums1
. This is because:
- The Counter object
cnt
stores at mostm
elements (all unique elements fromnums1
) - The output list
ans
stores at mostmin(m, n)
elements, which is bounded byO(m)
- Therefore, the dominant space usage is
O(m)
Common Pitfalls
1. Accessing Non-existent Keys in Dictionary
A common mistake is directly checking cnt[x]
when x
might not exist in the counter/dictionary, which can raise a KeyError
in standard dictionaries.
Problematic Code:
# Using a regular dict instead of Counter frequency_map = {} for num in nums1: frequency_map[num] = frequency_map.get(num, 0) + 1 for num in nums2: if frequency_map[num] > 0: # KeyError if num not in frequency_map! result.append(num) frequency_map[num] -= 1
Solution: Use proper existence checking or leverage Counter's default behavior:
# Option 1: Check key existence first if num in frequency_map and frequency_map[num] > 0: result.append(num) frequency_map[num] -= 1 # Option 2: Use get() method if frequency_map.get(num, 0) > 0: result.append(num) frequency_map[num] -= 1 # Option 3: Use Counter (handles missing keys gracefully) cnt = Counter(nums1) if cnt[num]: # Counter returns 0 for missing keys result.append(num) cnt[num] -= 1
2. Modifying the Wrong Array's Frequency
Another pitfall is accidentally counting elements from nums2
or modifying the wrong array's frequency map, especially when trying to optimize by counting the smaller array.
Problematic Code:
# Trying to optimize but introducing a bug
if len(nums1) > len(nums2):
nums1, nums2 = nums2, nums1 # Swap arrays
cnt = Counter(nums1)
result = []
# Bug: Still iterating through the original nums2 reference
for num in nums2: # This is now the longer array!
if cnt[num]:
result.append(num)
cnt[num] -= 1
Solution: Be consistent with which array you count and which you iterate:
# Correct optimization approach
if len(nums1) > len(nums2):
return self.intersect(nums2, nums1) # Recursive call with swapped parameters
# Or handle the swap more carefully
cnt = Counter(nums1)
result = []
for num in nums2:
if cnt[num]:
result.append(num)
cnt[num] -= 1
3. Not Handling Empty Arrays
While the Counter approach naturally handles empty arrays, explicitly checking can prevent unnecessary computation.
Enhancement:
# Early return for edge cases if not nums1 or not nums2: return []
Which of the following is a good use case for backtracking?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Want a Structured Path to Master System Design Too? Don’t Miss This!