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)wherexcomes fromnums1andycomes fromnums2. -
Check divisibility condition: For each pair, we check if
x % (y * k) == 0. This tells us whetherxis divisible by the product ofyandk. -
Count valid pairs: We use Python's
sum()function with a generator expression to count how many pairs satisfy the condition. EachTruevalue from the condition check contributes1to the sum, whileFalsecontributes0.
The one-liner implementation:
return sum(x % (y * k) == 0 for x in nums1 for y in nums2)
This expression:
- Uses
for x in nums1to iterate through each element in the first array - Uses
for y in nums2to iterate through each element in the second array (nested) - Evaluates
x % (y * k) == 0for each pair, producing a boolean value - The
sum()function counts theTruevalues (treatingTrueas1andFalseas0)
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
251class 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}
271class 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};
221/**
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}
27Time 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 [30, 20, 10, 100, 33, 12]?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
81public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
121class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85Recommended 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!