Facebook Pixel

3164. Find the Number of Good Pairs II

MediumArrayHash Table
Leetcode Link

Problem Description

You have two integer arrays nums1 and nums2 with lengths n and m respectively. You also have a positive integer k.

A pair (i, j) is considered good if nums1[i] is divisible by nums2[j] * k, where 0 <= i <= n - 1 and 0 <= j <= m - 1.

Your task is to find and return the total number of good pairs.

For example, if nums1[i] = 12, nums2[j] = 3, and k = 2, then this would be a good pair because 12 is divisible by 3 * 2 = 6 (since 12 % 6 = 0).

The solution uses hash tables to optimize the counting process. First, it filters elements from nums1 that are divisible by k and stores their quotients (after dividing by k) in a counter. Then, for each unique value in nums2, it counts how many of its multiples appear in the processed nums1 values. The key insight is that if nums1[i] is divisible by nums2[j] * k, then nums1[i] / k must be divisible by nums2[j]. By iterating through multiples of each value in nums2 up to the maximum value in the processed nums1, the solution efficiently counts all good pairs.

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

Intuition

The key observation is that checking if nums1[i] is divisible by nums2[j] * k for every pair would result in O(n*m) time complexity, which could be inefficient for large arrays.

Let's think about the divisibility condition differently. If nums1[i] is divisible by nums2[j] * k, we can rewrite this as:

  • nums1[i] % (nums2[j] * k) == 0
  • This means nums1[i] / k must be divisible by nums2[j]

But wait - for nums1[i] to be divisible by nums2[j] * k, it must first be divisible by k itself. If nums1[i] is not divisible by k, it can never form a good pair with any element from nums2.

This leads us to our first optimization: we only need to consider elements in nums1 that are divisible by k. For these elements, we can work with nums1[i] / k instead of nums1[i].

Now, instead of checking divisibility for each pair individually, we can reverse our thinking. For each value x in nums2, we want to find how many values in our modified nums1 (after dividing by k) are divisible by x. These would be exactly the multiples of x: x, 2x, 3x, 4x, ...

By using hash tables to count occurrences and then iterating through multiples, we avoid redundant checks. For each unique value in nums2, we find all its multiples that exist in the processed nums1 array, multiply by their respective frequencies, and sum up the results.

This approach transforms the problem from checking all pairs to efficiently counting multiples using frequency maps, significantly improving the time complexity.

Solution Approach

The implementation uses hash tables and the enumerate multiples pattern to efficiently count good pairs.

Step 1: Build the first counter

cnt1 = Counter(x // k for x in nums1 if x % k == 0)

We iterate through nums1 and filter elements divisible by k. For each such element x, we store x // k in the counter cnt1. This preprocessing step ensures we only work with relevant elements and simplifies our divisibility check later.

Step 2: Early termination check

if not cnt1:
    return 0

If no elements in nums1 are divisible by k, there can be no good pairs, so we return 0 immediately.

Step 3: Build the second counter and find maximum

cnt2 = Counter(nums2)
mx = max(cnt1)

We count the frequency of each element in nums2 using cnt2. We also find the maximum key in cnt1, which represents the largest value we need to check when enumerating multiples.

Step 4: Enumerate multiples for each unique value in nums2

for x, v in cnt2.items():
    s = sum(cnt1[y] for y in range(x, mx + 1, x))
    ans += s * v

For each unique value x in nums2 with frequency v:

  • We generate all multiples of x from x to mx using range(x, mx + 1, x). This gives us x, 2x, 3x, ... up to mx.
  • For each multiple y, we check if it exists in cnt1 and sum up all occurrences: sum(cnt1[y] for y in range(x, mx + 1, x)).
  • We multiply this sum s by the frequency v of x in nums2, since each occurrence of x in nums2 contributes to s good pairs.
  • We accumulate the result in ans.

The algorithm leverages the fact that instead of checking divisibility for every pair, we can count how many times each multiple appears and use multiplication to get the total count, reducing time complexity significantly.

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 a concrete example to illustrate the solution approach.

Given:

  • nums1 = [12, 6, 8, 24]
  • nums2 = [3, 2, 4]
  • k = 2

Step 1: Filter nums1 and divide by k

We check which elements in nums1 are divisible by k = 2:

  • 12 % 2 = 0 ✓ → 12 / 2 = 6
  • 6 % 2 = 0 ✓ → 6 / 2 = 3
  • 8 % 2 = 0 ✓ → 8 / 2 = 4
  • 24 % 2 = 0 ✓ → 24 / 2 = 12

So cnt1 = {6: 1, 3: 1, 4: 1, 12: 1}

Step 2: Build counter for nums2 and find maximum

cnt2 = {3: 1, 2: 1, 4: 1} mx = max(cnt1) = 12

Step 3: Count good pairs by enumerating multiples

For each unique value in nums2:

  • For x = 3 (frequency = 1):

    • Multiples of 3 up to 12: 3, 6, 9, 12
    • Check which exist in cnt1:
      • 3 exists → count = 1
      • 6 exists → count = 1
      • 9 doesn't exist → count = 0
      • 12 exists → count = 1
    • Sum = 1 + 1 + 0 + 1 = 3
    • Contribution = 3 × 1 = 3 good pairs
  • For x = 2 (frequency = 1):

    • Multiples of 2 up to 12: 2, 4, 6, 8, 10, 12
    • Check which exist in cnt1:
      • 2 doesn't exist → count = 0
      • 4 exists → count = 1
      • 6 exists → count = 1
      • 8 doesn't exist → count = 0
      • 10 doesn't exist → count = 0
      • 12 exists → count = 1
    • Sum = 0 + 1 + 1 + 0 + 0 + 1 = 3
    • Contribution = 3 × 1 = 3 good pairs
  • For x = 4 (frequency = 1):

    • Multiples of 4 up to 12: 4, 8, 12
    • Check which exist in cnt1:
      • 4 exists → count = 1
      • 8 doesn't exist → count = 0
      • 12 exists → count = 1
    • Sum = 1 + 0 + 1 = 2
    • Contribution = 2 × 1 = 2 good pairs

Total good pairs = 3 + 3 + 2 = 8

Verification of actual good pairs:

  1. (0,0): 12 % (3×2) = 12 % 6 = 0
  2. (0,1): 12 % (2×2) = 12 % 4 = 0
  3. (0,2): 12 % (4×2) = 12 % 8 = 4
  4. (1,0): 6 % (3×2) = 6 % 6 = 0
  5. (1,1): 6 % (2×2) = 6 % 4 = 2
  6. (1,2): 6 % (4×2) = 6 % 8 = 6
  7. (2,0): 8 % (3×2) = 8 % 6 = 2
  8. (2,1): 8 % (2×2) = 8 % 4 = 0
  9. (2,2): 8 % (4×2) = 8 % 8 = 0
  10. (3,0): 24 % (3×2) = 24 % 6 = 0
  11. (3,1): 24 % (2×2) = 24 % 4 = 0
  12. (3,2): 24 % (4×2) = 24 % 8 = 0

Count of ✓ = 8, which matches our calculated result!

Solution Implementation

1class Solution:
2    def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:
3        # Count frequency of nums1 elements divided by k (only for elements divisible by k)
4        # This optimization reduces nums1 values by factor of k
5        frequency_nums1_divided = Counter(num // k for num in nums1 if num % k == 0)
6      
7        # If no elements in nums1 are divisible by k, no valid pairs exist
8        if not frequency_nums1_divided:
9            return 0
10      
11        # Count frequency of elements in nums2
12        frequency_nums2 = Counter(nums2)
13      
14        # Initialize result counter
15        total_pairs = 0
16      
17        # Find the maximum value in the reduced nums1 (after dividing by k)
18        max_value = max(frequency_nums1_divided)
19      
20        # For each unique value in nums2, count how many valid pairs it forms
21        for nums2_value, nums2_count in frequency_nums2.items():
22            # Count how many multiples of nums2_value exist in frequency_nums1_divided
23            # These represent valid divisibility relationships
24            multiples_count = sum(
25                frequency_nums1_divided[multiple] 
26                for multiple in range(nums2_value, max_value + 1, nums2_value)
27            )
28          
29            # Add to total: (count of this value in nums2) * (count of valid pairs)
30            total_pairs += multiples_count * nums2_count
31      
32        return total_pairs
33
1class Solution {
2    public long numberOfPairs(int[] nums1, int[] nums2, int k) {
3        // Count frequency of nums1 elements divided by k (only for elements divisible by k)
4        Map<Integer, Integer> frequencyMap1 = new HashMap<>();
5        for (int num : nums1) {
6            if (num % k == 0) {
7                frequencyMap1.merge(num / k, 1, Integer::sum);
8            }
9        }
10      
11        // Early return if no elements in nums1 are divisible by k
12        if (frequencyMap1.isEmpty()) {
13            return 0;
14        }
15      
16        // Count frequency of elements in nums2
17        Map<Integer, Integer> frequencyMap2 = new HashMap<>();
18        for (int num : nums2) {
19            frequencyMap2.merge(num, 1, Integer::sum);
20        }
21      
22        // Calculate the number of valid pairs
23        long totalPairs = 0;
24        int maxValue = Collections.max(frequencyMap1.keySet());
25      
26        // For each unique value in nums2, check its multiples in frequencyMap1
27        for (Map.Entry<Integer, Integer> entry : frequencyMap2.entrySet()) {
28            int value = entry.getKey();
29            int frequency = entry.getValue();
30            int matchCount = 0;
31          
32            // Check all multiples of 'value' up to maxValue
33            for (int multiple = value; multiple <= maxValue; multiple += value) {
34                matchCount += frequencyMap1.getOrDefault(multiple, 0);
35            }
36          
37            // Add to total: number of matches * frequency of current value in nums2
38            totalPairs += (long) matchCount * frequency;
39        }
40      
41        return totalPairs;
42    }
43}
44
1class Solution {
2public:
3    long long numberOfPairs(vector<int>& nums1, vector<int>& nums2, int k) {
4        // Count frequency of elements in nums1 that are divisible by k
5        // Store the quotient (nums1[i] / k) as key
6        unordered_map<int, int> divisibleCount;
7        for (int num : nums1) {
8            if (num % k == 0) {
9                divisibleCount[num / k]++;
10            }
11        }
12      
13        // Early termination if no elements in nums1 are divisible by k
14        if (divisibleCount.empty()) {
15            return 0;
16        }
17      
18        // Count frequency of elements in nums2
19        unordered_map<int, int> nums2Count;
20        for (int num : nums2) {
21            nums2Count[num]++;
22        }
23      
24        // Find the maximum quotient value from divisibleCount
25        int maxQuotient = 0;
26        for (const auto& [quotient, frequency] : divisibleCount) {
27            maxQuotient = max(maxQuotient, quotient);
28        }
29      
30        // Calculate the total number of valid pairs
31        long long totalPairs = 0;
32        for (const auto& [value, frequency] : nums2Count) {
33            // For each value in nums2, count how many multiples of it exist in divisibleCount
34            long long multiplesCount = 0;
35          
36            // Check all multiples of 'value' up to maxQuotient
37            for (int multiple = value; multiple <= maxQuotient; multiple += value) {
38                if (divisibleCount.find(multiple) != divisibleCount.end()) {
39                    multiplesCount += divisibleCount[multiple];
40                }
41            }
42          
43            // Add to total: (number of valid nums1 elements) * (frequency of current nums2 value)
44            totalPairs += multiplesCount * frequency;
45        }
46      
47        return totalPairs;
48    }
49};
50
1/**
2 * Counts the number of valid pairs (i, j) where nums1[i] is divisible by nums2[j] * k
3 * @param nums1 - First array of numbers
4 * @param nums2 - Second array of numbers
5 * @param k - The divisor factor
6 * @returns The count of valid pairs
7 */
8function numberOfPairs(nums1: number[], nums2: number[], k: number): number {
9    // Count frequency of nums1 elements divided by k (only for elements divisible by k)
10    const dividedFrequencyMap: Map<number, number> = new Map();
11  
12    for (const num of nums1) {
13        // Only consider numbers from nums1 that are divisible by k
14        if (num % k === 0) {
15            const dividedValue = Math.floor(num / k);
16            dividedFrequencyMap.set(
17                dividedValue, 
18                (dividedFrequencyMap.get(dividedValue) || 0) + 1
19            );
20        }
21    }
22  
23    // Early return if no elements in nums1 are divisible by k
24    if (dividedFrequencyMap.size === 0) {
25        return 0;
26    }
27  
28    // Count frequency of elements in nums2
29    const nums2FrequencyMap: Map<number, number> = new Map();
30  
31    for (const num of nums2) {
32        nums2FrequencyMap.set(
33            num, 
34            (nums2FrequencyMap.get(num) || 0) + 1
35        );
36    }
37  
38    // Find the maximum value in the divided frequency map
39    const maxDividedValue = Math.max(...dividedFrequencyMap.keys());
40  
41    let totalPairs = 0;
42  
43    // For each unique value in nums2, count how many valid pairs it forms
44    for (const [nums2Value, nums2Count] of nums2FrequencyMap) {
45        let validPairsForValue = 0;
46      
47        // Check all multiples of nums2Value up to maxDividedValue
48        // If nums1[i] / k = multiple of nums2Value, then nums1[i] is divisible by nums2Value * k
49        for (let multiple = nums2Value; multiple <= maxDividedValue; multiple += nums2Value) {
50            validPairsForValue += dividedFrequencyMap.get(multiple) || 0;
51        }
52      
53        // Multiply by frequency of this value in nums2
54        totalPairs += validPairsForValue * nums2Count;
55    }
56  
57    return totalPairs;
58}
59

Time and Space Complexity

Time Complexity: O(n + m + (M/k) × √(M/k))

Breaking down the time complexity:

  • Creating cnt1: O(n) - iterates through nums1 once, with O(1) operations for division and modulo checks
  • Creating cnt2: O(m) - iterates through nums2 once
  • Finding mx = max(cnt1): O(n) in worst case when all elements in nums1 are divisible by k
  • The nested loop structure:
    • Outer loop iterates through each unique value in cnt2: at most O(m) iterations
    • Inner loop (the range operation) for each x iterates from x to mx with step x, which gives mx/x iterations
    • The sum of all inner loop iterations across all x values is bounded by the sum of divisors of all numbers up to mx
    • For a number mx = M/k, the sum of mx/1 + mx/2 + ... + mx/mx is O((M/k) × log(M/k)) using the harmonic series approximation

However, upon closer inspection, the reference answer suggests O(n + m + (M/k) × log m). This could be interpreted as considering that the number of unique values in cnt2 is at most m, and for each position up to M/k, we might need to check O(log m) divisors on average.

Space Complexity: O(n + m)

Breaking down the space complexity:

  • cnt1: O(n) in worst case when all elements in nums1 are divisible by k and unique after division
  • cnt2: O(m) in worst case when all elements in nums2 are unique
  • Other variables (ans, mx, s, x, v): O(1)

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

Common Pitfalls

1. Forgetting to Check Divisibility by k First

A common mistake is attempting to process all elements in nums1 without first filtering for divisibility by k. This leads to incorrect results or runtime errors.

Incorrect approach:

# Wrong: Dividing without checking divisibility first
frequency_nums1_divided = Counter(num // k for num in nums1)

Why it fails: If nums1[i] is not divisible by k, then the pair cannot be good regardless of nums2[j]. Including num // k when num % k != 0 will lead to counting invalid pairs.

Correct approach:

# Filter elements divisible by k before dividing
frequency_nums1_divided = Counter(num // k for num in nums1 if num % k == 0)

2. Integer Division vs Float Division Confusion

Using float division (/) instead of integer division (//) can cause issues with the Counter and subsequent calculations.

Incorrect approach:

# Wrong: Using float division
frequency_nums1_divided = Counter(num / k for num in nums1 if num % k == 0)

Why it fails: This creates float keys in the Counter (e.g., 6.0 instead of 6), which won't match properly when checking multiples using integer ranges.

Correct approach:

# Use integer division
frequency_nums1_divided = Counter(num // k for num in nums1 if num % k == 0)

3. Off-by-One Error in Range

When enumerating multiples, forgetting to include max_value itself in the range is a subtle but critical error.

Incorrect approach:

# Wrong: Excludes max_value
for multiple in range(nums2_value, max_value, nums2_value)

Why it fails: If max_value itself is a multiple of nums2_value, it won't be counted, leading to missing valid pairs.

Correct approach:

# Include max_value by adding 1
for multiple in range(nums2_value, max_value + 1, nums2_value)

4. Not Handling Empty Counter Case

Failing to check if frequency_nums1_divided is empty before finding the maximum can cause a runtime error.

Incorrect approach:

# Wrong: Assumes counter is non-empty
frequency_nums1_divided = Counter(num // k for num in nums1 if num % k == 0)
max_value = max(frequency_nums1_divided)  # ValueError if empty

Why it fails: If no elements in nums1 are divisible by k, the Counter is empty, and max() on an empty sequence raises a ValueError.

Correct approach:

frequency_nums1_divided = Counter(num // k for num in nums1 if num % k == 0)
if not frequency_nums1_divided:
    return 0
max_value = max(frequency_nums1_divided)

5. Incorrect Multiplication of Counts

A subtle error is forgetting to multiply by the frequency of values in nums2, treating each unique value as appearing only once.

Incorrect approach:

# Wrong: Not accounting for frequency in nums2
for nums2_value in set(nums2):  # Using set loses frequency information
    multiples_count = sum(
        frequency_nums1_divided[multiple] 
        for multiple in range(nums2_value, max_value + 1, nums2_value)
    )
    total_pairs += multiples_count  # Missing multiplication by frequency

Why it fails: Each occurrence of a value in nums2 can form pairs with all valid elements in nums1, so we need to multiply by its frequency.

Correct approach:

frequency_nums2 = Counter(nums2)
for nums2_value, nums2_count in frequency_nums2.items():
    multiples_count = sum(
        frequency_nums1_divided[multiple] 
        for multiple in range(nums2_value, max_value + 1, nums2_value)
    )
    total_pairs += multiples_count * nums2_count
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.

Which of the following method should we use to solve this problem?


Recommended Readings

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

Load More