3162. Find the Number of Good Pairs I
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 when nums1[i]
can be evenly divided by the product nums2[j] * k
. Here, i
is a valid index for nums1
(ranging from 0
to n - 1
) and j
is a valid index for nums2
(ranging from 0
to m - 1
).
In other words, a pair (i, j)
is good if nums1[i] % (nums2[j] * k) == 0
.
Your task is to count and return the total number of good pairs across all possible combinations of indices from both arrays.
For example, if nums1 = [12, 24]
, nums2 = [3, 4]
, and k = 2
:
- Pair
(0, 0)
: Check if12 % (3 * 2) == 0
→12 % 6 == 0
→ True (good pair) - Pair
(0, 1)
: Check if12 % (4 * 2) == 0
→12 % 8 == 0
→ False (not good) - Pair
(1, 0)
: Check if24 % (3 * 2) == 0
→24 % 6 == 0
→ True (good pair) - Pair
(1, 1)
: Check if24 % (4 * 2) == 0
→24 % 8 == 0
→ True (good pair)
The total number of good pairs would be 3.
Intuition
The problem asks us to find pairs where one number is divisible by another number multiplied by k
. This is fundamentally a divisibility checking problem.
Since we need to check every possible pair between the two arrays, the most straightforward approach is to examine each element from nums1
with each element from nums2
. This naturally leads us to think about nested loops or nested iterations.
For each element x
in nums1
, we need to check it against every element y
in nums2
to see if x
is divisible by y * k
. The divisibility check can be done using the modulo operator: if x % (y * k) == 0
, then x
is divisible by y * k
.
The brute force approach makes sense here because:
- We must examine all possible pairs to ensure we don't miss any good pairs
- Each pair requires a simple arithmetic check (multiplication and modulo operation)
- There's no apparent pattern or mathematical property that would allow us to skip certain pairs without checking
The solution can be elegantly expressed using a generator expression with two iterations - one over nums1
and one over nums2
. For each combination, we perform the divisibility check and count how many pairs satisfy the condition. This gives us a concise one-liner that directly translates our intuition into code: iterate through all pairs, check the condition, and count the successes.
Solution Approach
The solution uses a brute force enumeration approach to check all possible pairs between the two arrays.
Implementation Steps:
-
Iterate through all pairs: We use nested iteration to generate all possible pairs
(x, y)
wherex
comes fromnums1
andy
comes fromnums2
. -
Check divisibility condition: For each pair, we check if
x % (y * k) == 0
. This tells us whetherx
is divisible by the product ofy
andk
. -
Count valid pairs: We use Python's
sum()
function with a generator expression to count how many pairs satisfy the condition. EachTrue
value from the condition check contributes1
to the sum, whileFalse
contributes0
.
The one-liner implementation:
return sum(x % (y * k) == 0 for x in nums1 for y in nums2)
This expression:
- Uses
for x in nums1
to iterate through each element in the first array - Uses
for y in nums2
to iterate through each element in the second array (nested) - Evaluates
x % (y * k) == 0
for each pair, producing a boolean value - The
sum()
function counts theTrue
values (treatingTrue
as1
andFalse
as0
)
Time Complexity: O(n * m)
where n
is the length of nums1
and m
is the length of nums2
, since we check every possible pair.
Space Complexity: O(1)
as we only use a constant amount of extra space for the counting operation (the generator expression doesn't create an intermediate list).
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Given:
nums1 = [10, 20, 30]
nums2 = [2, 5]
k = 2
Step-by-step process:
We'll check every possible pair (i, j)
to see if nums1[i] % (nums2[j] * k) == 0
.
Iteration 1: x = 10
(from nums1)
- Pair with
y = 2
: Check10 % (2 * 2) = 10 % 4 = 2
→ Not divisible → Not a good pair - Pair with
y = 5
: Check10 % (5 * 2) = 10 % 10 = 0
→ Divisible → Good pair! ✓
Iteration 2: x = 20
(from nums1)
- Pair with
y = 2
: Check20 % (2 * 2) = 20 % 4 = 0
→ Divisible → Good pair! ✓ - Pair with
y = 5
: Check20 % (5 * 2) = 20 % 10 = 0
→ Divisible → Good pair! ✓
Iteration 3: x = 30
(from nums1)
- Pair with
y = 2
: Check30 % (2 * 2) = 30 % 4 = 2
→ Not divisible → Not a good pair - Pair with
y = 5
: Check30 % (5 * 2) = 30 % 10 = 0
→ Divisible → Good pair! ✓
Summary of pairs checked:
nums1[i] | nums2[j] | nums2[j] * k | nums1[i] % (nums2[j] * k) | Good? |
---|---|---|---|---|
10 | 2 | 4 | 2 | No |
10 | 5 | 10 | 0 | Yes |
20 | 2 | 4 | 0 | Yes |
20 | 5 | 10 | 0 | Yes |
30 | 2 | 4 | 2 | No |
30 | 5 | 10 | 0 | Yes |
Total good pairs: 4
The solution sum(x % (y * k) == 0 for x in nums1 for y in nums2)
would generate these exact checks in order, evaluating to False + True + True + True + False + True = 4
.
Solution Implementation
1class Solution:
2 def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:
3 """
4 Count the number of valid pairs (i, j) where nums1[i] is divisible by nums2[j] * k.
5
6 Args:
7 nums1: First list of integers
8 nums2: Second list of integers
9 k: Integer multiplier for elements in nums2
10
11 Returns:
12 Number of valid pairs where nums1[i] % (nums2[j] * k) == 0
13 """
14 # Initialize counter for valid pairs
15 count = 0
16
17 # Iterate through all possible pairs from nums1 and nums2
18 for num1 in nums1:
19 for num2 in nums2:
20 # Check if num1 is divisible by (num2 * k)
21 if num1 % (num2 * k) == 0:
22 count += 1
23
24 return count
25
1class Solution {
2 /**
3 * Counts the number of valid pairs (i, j) where nums1[i] is divisible by (nums2[j] * k)
4 *
5 * @param nums1 First array of integers
6 * @param nums2 Second array of integers
7 * @param k Multiplication factor for elements in nums2
8 * @return Total count of valid pairs where nums1[i] % (nums2[j] * k) == 0
9 */
10 public int numberOfPairs(int[] nums1, int[] nums2, int k) {
11 int pairCount = 0;
12
13 // Iterate through each element in the first array
14 for (int num1Element : nums1) {
15 // Check against each element in the second array
16 for (int num2Element : nums2) {
17 // Check if num1Element is divisible by (num2Element * k)
18 if (num1Element % (num2Element * k) == 0) {
19 pairCount++;
20 }
21 }
22 }
23
24 return pairCount;
25 }
26}
27
1class Solution {
2public:
3 int numberOfPairs(vector<int>& nums1, vector<int>& nums2, int k) {
4 int pairCount = 0;
5
6 // Iterate through each element in the first array
7 for (int num1 : nums1) {
8 // For each element in nums1, check against all elements in nums2
9 for (int num2 : nums2) {
10 // Check if num1 is divisible by (num2 * k)
11 // If divisible, it forms a valid pair
12 if (num1 % (num2 * k) == 0) {
13 ++pairCount;
14 }
15 }
16 }
17
18 // Return the total count of valid pairs
19 return pairCount;
20 }
21};
22
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 - Multiplication factor
6 * @returns The count of valid pairs
7 */
8function numberOfPairs(nums1: number[], nums2: number[], k: number): number {
9 // Initialize counter for valid pairs
10 let validPairCount: number = 0;
11
12 // Iterate through each element in the first array
13 for (const num1Element of nums1) {
14 // Check against each element in the second array
15 for (const num2Element of nums2) {
16 // Check if num1Element is divisible by (num2Element * k)
17 if (num1Element % (num2Element * k) === 0) {
18 // Increment counter when divisibility condition is met
19 validPairCount++;
20 }
21 }
22 }
23
24 // Return the total count of valid pairs
25 return validPairCount;
26}
27
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 the code uses a nested loop structure with a generator expression - for each element x
in nums1
, it iterates through every element y
in nums2
, performing a constant-time modulo operation x % (y * k) == 0
for each pair. Therefore, the total number of operations is m × n
.
The space complexity is O(1)
. The generator expression doesn't create any intermediate data structures and only uses a constant amount of extra space for the loop variables x
and y
, and for accumulating the sum. The sum()
function processes the generator expression iteratively without storing all results in memory.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow with Large Values
When computing num2 * k
, the multiplication could potentially overflow if both num2
and k
are large integers. While Python handles arbitrary precision integers automatically, in other languages or when optimizing, this could cause incorrect results.
Example Issue:
# If num2 = 10^9 and k = 10^9, their product is 10^18 # In languages with fixed integer sizes, this would overflow
Solution:
Check if the product would exceed num1
before performing the modulo operation:
for num1 in nums1: for num2 in nums2: # Avoid unnecessary computation if num2 * k > num1 if num2 <= num1 // k: # Prevents overflow and improves efficiency if num1 % (num2 * k) == 0: count += 1
2. Division by Zero
If any element in nums2
is zero or if k
is zero, the expression num1 % (num2 * k)
would attempt modulo by zero, causing a runtime error.
Solution: Add validation or handle zero cases explicitly:
def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:
# Handle edge case where k is 0
if k == 0:
return 0
count = 0
for num1 in nums1:
for num2 in nums2:
# Skip if num2 is 0 to avoid modulo by zero
if num2 == 0:
continue
if num1 % (num2 * k) == 0:
count += 1
return count
3. Performance Degradation with Large Arrays
The O(n*m) time complexity becomes problematic when both arrays are large (e.g., 10^5 elements each), resulting in 10^10 operations.
Solution: For better performance with large inputs, use factorization-based optimization:
def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:
from collections import Counter
# Count occurrences of each value in nums2
nums2_count = Counter(nums2)
count = 0
for num1 in nums1:
# Only check divisors of num1/k if num1 is divisible by k
if num1 % k == 0:
quotient = num1 // k
# Find all divisors of quotient that appear in nums2
for i in range(1, int(quotient**0.5) + 1):
if quotient % i == 0:
# i is a divisor
if i in nums2_count:
count += nums2_count[i]
# quotient/i is also a divisor (if different from i)
if i != quotient // i and quotient // i in nums2_count:
count += nums2_count[quotient // i]
return count
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
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 assets algo monster recursion jpg You first call Ben and ask
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!