Facebook Pixel

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] and nums2 = [2,2], the intersection would be [2,2] because the number 2 appears twice in both arrays.
  • If nums1 = [4,9,5] and nums2 = [9,4,9,8,4], the intersection would be [4,9] or [9,4] (order doesn't matter) because 4 appears once in nums1 and twice in nums2 (so we take 1), and 9 appears once in nums1 and twice in nums2 (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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 (meaning x exists in nums1 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 to True
  • If x doesn't exist or has value 0, it evaluates to False

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 Evaluator

Example 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 takes O(m) time
  • Iterating through each element in nums2 takes O(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 most m elements (all unique elements from nums1)
  • The output list ans stores at most min(m, n) elements, which is bounded by O(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 []
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following is a good use case for backtracking?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More