3164. Find the Number of Good Pairs II
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
:
-
Hash Table for
nums1
:- We create a hash table
cnt1
where the key is the quotient of each element innums1
divided byk
. - We only include elements from
nums1
that are divisible byk
, i.e.,x % k == 0
, and store the frequency of each resulting quotient fromx // k
.
- We create a hash table
-
Hash Table for
nums2
:- We create another hash table
cnt2
to store the count of each number innums2
.
- We create another hash table
-
Iterating Over
nums2
:- For each number
x
innums2
, identified fromcnt2
, we iterate over its multiples, starting fromx
up to the maximum value encountered incnt1
(let's call thismx
).
- For each number
-
Counting Good Pairs:
- For each multiplier
y
in the potential multiples ofx
, we sum up the counts fromcnt1
for thaty
. - We multiply this sum by the occurrence count of
x
innums2
(found incnt2[x]
) to calculate the contribution of these combinations to the total number of good pairs.
- For each multiplier
-
Final Calculation:
- By adding up all these contributions for each distinct
x
innums2
, we obtain the total number of "good" pairs.
- By adding up all these contributions for each distinct
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 EvaluatorExample Walkthrough
Let's use a small example to illustrate the solution approach:
Suppose nums1 = [4, 8, 16]
, nums2 = [1, 2, 4]
, and k = 2
.
-
Creating Hash Table
cnt1
fornums1
:- Check each element in
nums1
to see if it's divisible byk
. - For
4
, since4 % 2 == 0
, add the quotient4 // 2 = 2
tocnt1
, socnt1 = {2: 1}
. - For
8
,8 % 2 == 0
, add the quotient8 // 2 = 4
tocnt1
, updating it tocnt1 = {2: 1, 4: 1}
. - For
16
,16 % 2 == 0
, add the quotient16 // 2 = 8
tocnt1
, socnt1 = {2: 1, 4: 1, 8: 1}
.
- Check each element in
-
Creating Hash Table
cnt2
fornums2
:- Count each number in
nums2
. - For
1
, add it tocnt2
, thuscnt2 = {1: 1}
. - For
2
, add it, updatingcnt2 = {1: 1, 2: 1}
. - For
4
, add it, updatingcnt2 = {1: 1, 2: 1, 4: 1}
.
- Count each number in
-
Iterating Over
nums2
:- For each element
x
incnt2
, examine its multiples. - The maximum key in
cnt1
is8
.
- For each element
-
Counting Good Pairs:
-
For
x = 1
innums2
:- Multipliers start at
1
and go to8
. - At multiplier
2
, foundcnt1[2] = 1
, contributes1 * cnt2[1] = 1
good pair. - At multiplier
4
, foundcnt1[4] = 1
, also contributes1 * cnt2[1] = 1
good pair. - At multiplier
8
, foundcnt1[8] = 1
, also contributes1 * cnt2[1] = 1
good pair.
- Multipliers start at
-
For
x = 2
innums2
:- Multipliers start at
2
and go to8
. - At multiplier
4
,cnt1[4] = 1
, contributes1 * cnt2[2] = 1
good pair. - At multiplier
8
,cnt1[8] = 1
, contributes1 * cnt2[2] = 1
good pair.
- Multipliers start at
-
For
x = 4
innums2
:- Multipliers start at
4
and go to8
. - At multiplier
8
,cnt1[8] = 1
, contributes1 * cnt2[4] = 1
good pair.
- Multipliers start at
-
-
Final Calculation:
- Sum of all good pairs:
3 (for x = 1) + 2 (for x = 2) + 1 (for x = 4) = 6
.
- Sum of all good pairs:
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 thenums1
array.m
is the length of thenums2
array.M
is the maximum value in thenums1
array.(M / k) \times \log m
comes from the loop iterating over possible multiples of values innums2
and computing the sum from thecnt1
counter, assuming a binary search or sorted map operation.
The space complexity is O(n + m)
, where:
O(n)
comes from storing values incnt1
, which can have up ton
unique values.O(m)
comes from storing values incnt2
, which can have up tom
unique values.
Learn more about how to find time and space complexity quickly.
Which type of traversal does breadth first search do?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!