2248. Intersection of Multiple Arrays

EasyArrayHash TableCounting
Leetcode Link

Problem Description

In this problem, we are given a 2D array nums, where each subarray nums[i] consists of distinct positive integers. The goal is to find all the integers that are common in each of these subarrays and return a sorted list of these common elements. To clarify, an integer is part of the result list if and only if it is present in every subarray of nums.

For example, if nums = [[4,9,5],[9,4,9,8,4],[4,6,8,9]], the integers 4 and 9 are present in all the subarrays, so the sorted result would be [4,9].

Intuition

The intuition behind the solution is based on the concept of counting frequency. Since we are looking for integers that are present in all arrays, we can keep a counter for each number to track how many times it appears across all arrays. If a number appears in each subarray, its count should be equal to the number of subarrays in nums.

Here is the step-by-step approach:

  1. Initialize a counter array cnt which has a fixed size of 1001 to cover the potential range of values in nums subarrays as specified by the problem (assuming the maximum possible integer is 1000).

  2. Iterate over each subarray in nums and then iterate over each integer within that subarray and increment its corresponding count in cnt.

  3. After counting, iterate through the counter array cnt and pick those integers whose counter value is equal to the length of nums, which means these integers have appeared in each subarray.

  4. Collect these common integers and return them in a sorted order. It is implicit that the integers are already in sorted order because we increment the counts while iterating over the fixed range of indices from 1 to 1000. Thus, the resulting list of common integers is inherently sorted.

  5. The final result is a list of integers that appear in every subarray of nums, sorted in ascending order.

The solution effectively reduces the problem to a simple frequency counting mechanism and leverages the fixed range of possible integer values to ensure sorted output.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece:

How does quick sort divide the problem into subproblems?

Solution Approach

The given solution uses a simple counting approach using an array to keep track of the frequency of integers across all subarrays in nums. Let's break down the implementation into detailed steps:

  1. A counter array cnt is initialized with 1001 elements, all set to 0. This array acts as a hash table to keep track of the frequency of each integer since we know the integers are positive and the constraint hint at a maximum integer value of 1000.

  2. The outer for loop iterates through each subarray arr within the main array nums. For each subarray, we iterate through its elements using the inner for loop.

  3. Inside the inner for loop, we use the integer x from the subarray as an index to increment the counter at that index in cnt array by 1. This step counts the occurrence of each integer across all subarrays.

    1for arr in nums:
    2    for x in arr:
    3        cnt[x] += 1
  4. After filling the cnt array with counts, a list comprehension is used to iterate over all possible integers (using enumerate(cnt) which gives us both the number x and its count v), checking if the count v is equal to the length of nums. This ensures that we only select integers that are present in all subarrays.

    1return [x for x, v in enumerate(cnt) if v == len(nums)]
  5. The list comprehension also takes care of assembling the output list and the nature of enumeration guarantees the order will be ascending, hence satisfying the problem requirements of returning the common elements in sorted order.

The data structures utilized here are:

  • An array (cnt) used as a frequency counter.
  • A list comprehension to generate the output list.

The algorithm is a well-known counting sort technique which is very efficient when the range of potential values is known and reasonably small, as it is in this case. As a result, the complexity of this solution is very good, with time complexity being O(N * M) where N is the number of subarrays and M is the average length of these subarrays, and space complexity is O(1) since the size of the cnt array is fixed and does not grow with the input size.

Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:

What is the running time of the following code?

1int sqrt(int n) {
2  for (int guess = 1; guess * guess <= n; guess++) {
3    if (guess * guess == n) {
4      return guess;
5    }
6  }
7  return -1;
8}

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Suppose we have the following 2D array:

1nums = [[1, 2, 3], [2, 3, 4], [3, 4, 5, 6]]

The aim is to find all numbers that appear in every subarray.

  1. Initialize a counter array cnt with size 1001, all elements set to 0.

  2. We go through each subarray arr in nums. For the first subarray [1, 2, 3], we increment cnt[1], cnt[2], and cnt[3] by 1. After processing the first subarray, cnt looks like this:

    1cnt = [..., 0, 1, 1, 1, 0, 0, ...]  (positions 1, 2, and 3 are incremented)
  3. Repeat step 2 for the second subarray [2, 3, 4]. Now, cnt[2], cnt[3], and cnt[4] are incremented by 1, resulting in:

    1cnt = [..., 0, 1, 2, 2, 1, 0, ...]  (positions 2, 3, and 4 are incremented)
  4. Do the same for the third subarray [3, 4, 5, 6], incrementing cnt[3], cnt[4], cnt[5], and cnt[6]. After this step, cnt is:

    1cnt = [..., 0, 1, 2, 3, 2, 1, 1, 0, ...]  (positions 3, 4, 5, and 6 are incremented)
  5. After processing all subarrays, we iterate through the counter array cnt and look for values that are equal to the length of nums (which is 3 in this case). We find that cnt[3] is 3.

  6. The final output list will only include the number 3 as it is the only number whose count is equal to the number of subarrays in nums. We return [3].

The solution code that accomplishes this is:

1cnt = [0] * 1001
2for arr in nums:
3   for x in arr:
4       cnt[x] += 1
5return [x for x, v in enumerate(cnt) if v == len(nums)]

And our example will yield the output:

1[3]

This simple example demonstrates how the counting approach finds common elements present in all subarrays reliably and efficiently.

Solution Implementation

1from typing import List
2
3class Solution:
4    def intersection(self, nums: List[List[int]]) -> List[int]:
5        # Initialize a list to count the occurrences of each number, assuming numbers range from 0 to 1000.
6        # This means each index represents a number, and the value at that index represents its count.
7        count = [0] * 1001
8      
9        # Loop through each list in the list of lists `nums`.
10        for num_list in nums:
11            # Using a set to avoid counting duplicates in the same list.
12            unique_nums = set(num_list)
13            # Increment the count for each number found in the list.
14            for num in unique_nums:
15                count[num] += 1
16      
17        # Return a list of numbers (index) where the count is equal to the length of `nums`
18        # i.e., the number appeared in all lists.
19        return [num for num, frequency in enumerate(count) if frequency == len(nums)]
20
1import java.util.ArrayList;
2import java.util.List;
3
4class Solution {
5    public List<Integer> intersection(int[][] arrays) {
6        // Array to store the count of each element (assuming the range of elements is 0-1000)
7        int[] count = new int[1001];
8      
9        // Iterate through each sub-array
10        for (int[] array : arrays) {
11            // Count each element in the sub-array
12            for (int element : array) {
13                ++count[element];
14            }
15        }
16      
17        // List to store the result (elements present in all sub-arrays)
18        List<Integer> result = new ArrayList<>();
19      
20        // Iterate through the count array to find elements with a count equal to the number of arrays,
21        // which means they appear in every sub-array.
22        for (int i = 0; i < 1001; ++i) {
23            if (count[i] == arrays.length) {
24                result.add(i); // Add to the result list if element is present in all arrays
25            }
26        }
27      
28        // Return the result list
29        return result;
30    }
31}
32
1#include <vector> // Include the required header for the vector container.
2
3class Solution {
4public:
5    // The function 'intersection' takes a vector of vector of ints as input.
6    // It returns a vector of integers that are common in all inner vectors.
7    vector<int> intersection(vector<vector<int>>& nums) {
8        // We use an array to keep count of integer occurrences across all inner vectors.
9        int countArray[1001] = {}; // There are 1001 elements initialized to 0.
10      
11        // Iterate over the outer vector 'nums', which contains inner vectors.
12        for (auto& innerArray : nums) {
13            // For each integer in the inner vectors, increment its count in 'countArray'.
14            for (int num : innerArray) {
15                countArray[num]++;
16            }
17        }
18
19        // Define a vector to hold the intersection results.
20        vector<int> intersectionResult;
21      
22        // Iterate over the 'countArray' to check which numbers have a count
23        // equal to the size of the outer vector (i.e., they appear in all inner vectors).
24        for (int i = 0; i < 1001; ++i) {
25            if (countArray[i] == nums.size()) {
26                intersectionResult.push_back(i); // Add the number to the result vector.
27            }
28        }
29
30        // Return the resulting intersection vector.
31        return intersectionResult;
32    }
33};
34
1function intersection(nums: number[][]): number[] {
2    // Create an array to keep track of the count of each number across all subarrays
3    const count = new Array(1001).fill(0);
4  
5    // Iterate over each subarray in nums
6    for (const subArray of nums) {
7        // Iterate over each number in the subarray
8        for (const num of subArray) {
9            // Increment the count for this number
10            count[num]++;
11        }
12    }
13  
14    // Prepare an array to hold the numbers present in all subarrays
15    const result: number[] = [];
16  
17    // Iterate over the possible numbers
18    for (let i = 0; i < 1001; i++) {
19        // If the count of a number is equal to the length of nums,
20        // it means the number is present in all subarrays
21        if (count[i] === nums.length) {
22            result.push(i);
23        }
24    }
25  
26    // Return the result array containing the intersection
27    return result;
28}
29
Not Sure What to Study? Take the 2-min Quiz:

Which of the following shows the order of node visit in a Breadth-first Search?

Time and Space Complexity

The time complexity of the provided code is O(n * k + m), where n represents the number of arrays in nums, k is the average length of the arrays in nums, and m is the fixed size of counting array cnt (1001 in this case). The first term n * k comes from the nested loops where we iterate over all elements across all the arrays. The second term m is due to the list comprehension at the end, which iterates over the whole cnt array once.

The space complexity of the code is O(m). This is because we use a fixed-size array cnt to count occurrences of each number (between 0 and 1000, inclusively). Space complexity does not depend on the input size, only on the size of the counting array cnt.

Learn more about how to find time and space complexity quickly using problem constraints.

Fast Track Your Learning with Our Quick Skills Quiz:

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


Recommended Readings


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫