Facebook Pixel

3164. Find the Number of Good Pairs II

MediumArrayHash Table
Leetcode Link

Problem Description

You are given two integer arrays nums1 and nums2 of lengths n and m respectively, along with a positive integer k. The task is to determine the total number of "good" pairs. A pair (i, j) is considered "good" if the element nums1[i] is divisible by nums2[j] * k. Your goal is to find the total count of such "good" pairs.

Intuition

To solve this problem, we need an efficient way to count how many times each condition for a "good" pair is satisfied. We start by examining the elements of nums1 and find the quotient of each element divided by k, only considering those elements that are perfectly divisible by k. This helps us count the occurrence of each possible quotient, which we'll represent as cnt1.

For nums2, we count how often each number appears, which is stored in cnt2.

With these counts, we iterate through each unique number x in nums2 and calculate its multiples up to the maximum key value found in cnt1. For each multiplier y, we determine how often y appears as a quotient in cnt1, accumulate this sum, and multiply it by the count of x in nums2 (from cnt2). Adding these products for each x gives the total number of "good" pairs.

The solution employs hash tables, cnt1 and cnt2, to efficiently manage counting and computational logic, ensuring that we derive the solution efficiently.

Solution Approach

The solution utilizes hash tables to efficiently track and manage the necessary counts of numbers from both nums1 and nums2:

  1. Hash Table for nums1:

    • We create a hash table cnt1 where the key is the quotient of each element in nums1 divided by k.
    • We only include elements from nums1 that are divisible by k, i.e., x % k == 0, and store the frequency of each resulting quotient from x // k.
  2. Hash Table for nums2:

    • We create another hash table cnt2 to store the count of each number in nums2.
  3. Iterating Over nums2:

    • For each number x in nums2, identified from cnt2, we iterate over its multiples, starting from x up to the maximum value encountered in cnt1 (let's call this mx).
  4. Counting Good Pairs:

    • For each multiplier y in the potential multiples of x, we sum up the counts from cnt1 for that y.
    • We multiply this sum by the occurrence count of x in nums2 (found in cnt2[x]) to calculate the contribution of these combinations to the total number of good pairs.
  5. Final Calculation:

    • By adding up all these contributions for each distinct x in nums2, we obtain the total number of "good" pairs.

The algorithm leverages efficient counting and loop iteration to ensure optimal performance, focusing on dividing the problem into manageable parts using hash tables.

Ready to land your dream job?

Unlock your dream job with a 2-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's use a small example to illustrate the solution approach:

Suppose nums1 = [4, 8, 16], nums2 = [1, 2, 4], and k = 2.

  1. Creating Hash Table cnt1 for nums1:

    • Check each element in nums1 to see if it's divisible by k.
    • For 4, since 4 % 2 == 0, add the quotient 4 // 2 = 2 to cnt1, so cnt1 = {2: 1}.
    • For 8, 8 % 2 == 0, add the quotient 8 // 2 = 4 to cnt1, updating it to cnt1 = {2: 1, 4: 1}.
    • For 16, 16 % 2 == 0, add the quotient 16 // 2 = 8 to cnt1, so cnt1 = {2: 1, 4: 1, 8: 1}.
  2. Creating Hash Table cnt2 for nums2:

    • Count each number in nums2.
    • For 1, add it to cnt2, thus cnt2 = {1: 1}.
    • For 2, add it, updating cnt2 = {1: 1, 2: 1}.
    • For 4, add it, updating cnt2 = {1: 1, 2: 1, 4: 1}.
  3. Iterating Over nums2:

    • For each element x in cnt2, examine its multiples.
    • The maximum key in cnt1 is 8.
  4. Counting Good Pairs:

    • For x = 1 in nums2:

      • Multipliers start at 1 and go to 8.
      • At multiplier 2, found cnt1[2] = 1, contributes 1 * cnt2[1] = 1 good pair.
      • At multiplier 4, found cnt1[4] = 1, also contributes 1 * cnt2[1] = 1 good pair.
      • At multiplier 8, found cnt1[8] = 1, also contributes 1 * cnt2[1] = 1 good pair.
    • For x = 2 in nums2:

      • Multipliers start at 2 and go to 8.
      • At multiplier 4, cnt1[4] = 1, contributes 1 * cnt2[2] = 1 good pair.
      • At multiplier 8, cnt1[8] = 1, contributes 1 * cnt2[2] = 1 good pair.
    • For x = 4 in nums2:

      • Multipliers start at 4 and go to 8.
      • At multiplier 8, cnt1[8] = 1, contributes 1 * cnt2[4] = 1 good pair.
  5. Final Calculation:

    • Sum of all good pairs: 3 (for x = 1) + 2 (for x = 2) + 1 (for x = 4) = 6.

Thus, the total number of "good" pairs is 6.

Solution Implementation

1from typing import List
2from collections import Counter
3
4class Solution:
5    def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:
6        # Create a Counter to count how many numbers in nums1 are divisible by k.
7        # Each entry contains the quotient when the numbers are divided by k.
8        cnt1 = Counter(x // k for x in nums1 if x % k == 0)
9      
10        # If there are no numbers in nums1 divisible by k, return 0.
11        if not cnt1:
12            return 0
13      
14        # Create a Counter for nums2, which records the frequency of each number.
15        cnt2 = Counter(nums2)
16      
17        # Initialize the answer to be 0.
18        ans = 0
19      
20        # Find the maximum key in cnt1. These keys are the quotients we calculated earlier.
21        mx = max(cnt1)
22      
23        # Iterate over each number and its frequency from nums2's Counter.
24        for x, v in cnt2.items():
25            # Initiate sum 's' to accumulate the count from cnt1.
26            # Loop to get sums of cnt1 for each multiple of x from x to mx inclusive.
27            s = sum(cnt1[y] for y in range(x, mx + 1, x))
28          
29            # For each number in nums2, increase the answer by the product of the sum 's' and the frequency of that number.
30            ans += s * v
31      
32        return ans
33
1import java.util.HashMap;
2import java.util.Map;
3import java.util.Collections;
4
5class Solution {
6    public long numberOfPairs(int[] nums1, int[] nums2, int k) {
7        // Map to store counts of elements in nums1 that are divisible by k.
8        Map<Integer, Integer> counts1 = new HashMap<>();
9        for (int num : nums1) {
10            if (num % k == 0) {
11                counts1.merge(num / k, 1, Integer::sum); // Increment count of num/k in the map.
12            }
13        }
14      
15        // If no element in nums1 is divisible by k, return 0.
16        if (counts1.isEmpty()) {
17            return 0;
18        }
19      
20        // Map to store counts of elements in nums2.
21        Map<Integer, Integer> counts2 = new HashMap<>();
22        for (int num : nums2) {
23            counts2.merge(num, 1, Integer::sum); // Increment count of num in the map.
24        }
25      
26        long result = 0;
27        // Find maximum key in counts1.
28        int maxKey = Collections.max(counts1.keySet());
29      
30        // Iterate over each entry in counts2.
31        for (Map.Entry<Integer, Integer> entry : counts2.entrySet()) {
32            int key = entry.getKey(); // Element from nums2.
33            int value = entry.getValue(); // Frequency of the element.
34            int sum = 0;
35          
36            // Iterate over multiples of 'key' in the possible range.
37            for (int multiple = key; multiple <= maxKey; multiple += key) {
38                sum += counts1.getOrDefault(multiple, 0); // Add counts from counts1 to sum.
39            }
40          
41            // Multiply frequency with sum to get the number of pairs contributed by this 'key'.
42            result += 1L * sum * value;
43        }
44      
45        // Return the computed result.
46        return result;
47    }
48}
49
1class Solution {
2public:
3    // Method to calculate the number of pairs based on provided conditions
4    long long numberOfPairs(vector<int>& nums1, vector<int>& nums2, int k) {
5        unordered_map<int, int> countDivisibles;  // Hashmap to count occurrences of elements divisible by k in nums1
6
7        // Traverse nums1 to count elements divisible by k
8        for (int num : nums1) {
9            if (num % k == 0) {  // Check if num is divisible by k
10                countDivisibles[num / k]++;  // Increment the count for num divided by k
11            }
12        }
13
14        // If there are no elements divisible by k, there cannot be any valid pairs
15        if (countDivisibles.empty()) {
16            return 0;
17        }
18
19        unordered_map<int, int> countNums2;  // Hashmap to count occurrences of each element in nums2
20
21        // Traverse nums2 to populate countNums2
22        for (int num : nums2) {
23            countNums2[num]++;  // Increment the count for each number
24        }
25
26        int maxValue = 0;  // Variable to store the maximum quotient found in countDivisibles
27
28        // Determine the maximum quotient from countDivisibles
29        for (auto& [quotient, _] : countDivisibles) {
30            maxValue = max(maxValue, quotient);
31        }
32
33        long long totalPairs = 0;  // Initialize total pairs counter
34
35        // Iterate through each element in nums2's count map
36        for (auto& [num, frequency] : countNums2) {
37            long long sum = 0;  // Initialize the sum of counts
38
39            // Compute the sum of possible divisible matches
40            for (int y = num; y <= maxValue; y += num) {
41                sum += countDivisibles[y];
42            }
43
44            // Multiply the sum with its frequency in nums2 to get the number of valid pairs for this element
45            totalPairs += sum * frequency;
46        }
47
48        return totalPairs;  // Return the total number of valid pairs
49    }
50};
51
1/**
2 * Function that calculates the number of valid pairs (i, j) such that nums1[i] is divisible by nums2[j] * k.
3 * 
4 * @param nums1 - Array of numbers for the first set of numbers.
5 * @param nums2 - Array of numbers for the second set of numbers.
6 * @param k - The divisor factor.
7 * @returns The number of valid pairs.
8 */
9function numberOfPairs(nums1: number[], nums2: number[], k: number): number {
10    // Create a map to store counts of elements from nums1 that are divisible by k.
11    const cnt1: Map<number, number> = new Map();
12
13    // Populate cnt1 with elements from nums1 that are divisible by k.
14    for (const num of nums1) {
15        if (num % k === 0) {
16            const key = (num / k) | 0;
17            cnt1.set(key, (cnt1.get(key) || 0) + 1);
18        }
19    }
20
21    // If no elements in nums1 are divisible by k, return 0.
22    if (cnt1.size === 0) {
23        return 0;
24    }
25
26    // Create a map to store counts of each number in nums2.
27    const cnt2: Map<number, number> = new Map();
28  
29    // Populate cnt2 with counts of elements from nums2.
30    for (const num of nums2) {
31        cnt2.set(num, (cnt2.get(num) || 0) + 1);
32    }
33  
34    // Find the maximum key in cnt1 to limit the search range.
35    const maxCnt1Key = Math.max(...cnt1.keys());
36
37    let totalPairs = 0;
38
39    // Iterate through each element in cnt2.
40    for (const [num2, count2] of cnt2) {
41        let subTotal = 0;
42
43        // Check multiples of num2 up to the maximum key in cnt1.
44        for (let multiple = num2; multiple <= maxCnt1Key; multiple += num2) {
45            subTotal += cnt1.get(multiple) || 0;
46        }
47
48        // Update the total with the product of subTotal and the count from cnt2.
49        totalPairs += subTotal * count2;
50    }
51
52    // Return the total number of valid pairs.
53    return totalPairs;
54}
55

Time and Space Complexity

The time complexity is O(n + m + (M / k) \times \log m), where:

  • n is the length of the nums1 array.
  • m is the length of the nums2 array.
  • M is the maximum value in the nums1 array.
  • (M / k) \times \log m comes from the loop iterating over possible multiples of values in nums2 and computing the sum from the cnt1 counter, assuming a binary search or sorted map operation.

The space complexity is O(n + m), where:

  • O(n) comes from storing values in cnt1, which can have up to n unique values.
  • O(m) comes from storing values in cnt2, which can have up to m unique values.

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


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

Which type of traversal does breadth first search do?


Recommended Readings

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


Load More