Count Array Pairs Divisible by K
HardPrompt
Given a 0-indexed integer array nums of length n and an integer k, return the number of pairs (i, j) such that:
0 <= i < j <= n - 1andnums[i] * nums[j]is divisible byk.
Example 1:
Input: nums = [1,2,3,4,5], k = 2
Output: 7
Explanation:
The 7 pairs of indices whose corresponding products are divisible by 2 are
(0, 1), (0, 3), (1, 2), (1, 3), (1, 4), (2, 3), and (3, 4).
Their products are 2, 4, 6, 8, 10, 12, and 20 respectively.
Other pairs such as (0, 2) and (2, 4) have products 3 and 15 respectively, which are not divisible by 2. Example 2:
Input: nums = [1,2,3,4], k = 5
Output: 0
Explanation: There does not exist any pair of indices whose corresponding product is divisible by 5.
Constraints:
1 <= nums.length <= 1051 <= nums[i], k <= 105
Approaches
3 approaches with complexity analysis and trade-offs.
The most straightforward way to solve this problem is to check every possible pair of elements in the array. This approach uses two nested loops to generate all pairs (i, j) such that 0 <= i < j < n. For each pair, it computes their product and checks if it's divisible by k.
Algorithm
- Initialize a counter
countto 0. - Use a nested loop to iterate through all unique pairs of indices
(i, j)wherei < j. - For each pair, calculate the product
p = (long)nums[i] * nums[j]. It's important to cast tolongto prevent potential integer overflow sincenums[i]andnums[j]can be up to 10<sup>5</sup>. - Check if the product
pis divisible bykusing the modulo operator:p % k == 0. - If the product is divisible by
k, increment thecount. - After checking all pairs, return the final
count.
Walkthrough
This method involves a brute-force check of all possible pairs. The outer loop runs from the first element to the second-to-last element, and the inner loop runs from the element after the outer loop's current element to the last one. This ensures that every pair (i, j) with i < j is considered exactly once. Inside the inner loop, we perform the multiplication and the divisibility check. A counter is maintained to keep track of the number of valid pairs found.
class Solution { public long countPairs(int[] nums, int k) { long count = 0; int n = nums.length; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (((long) nums[i] * nums[j]) % k == 0) { count++; } } } return count; }}Complexity
Time
O(N<sup>2</sup>) - Where N is the number of elements in `nums`. The nested loops result in a quadratic number of operations, as we check every possible pair.
Space
O(1) - We only use a constant amount of extra space for the counter and loop variables.
Trade-offs
Pros
Simple to understand and implement.
Requires no extra space besides a few variables.
Cons
This approach is very slow and will result in a 'Time Limit Exceeded' (TLE) error on platforms like LeetCode for the given constraints (N up to 10<sup>5</sup>).
A more optimized approach uses number theory, specifically the greatest common divisor (GCD). The core insight is that the condition (a * b) % k == 0 is equivalent to (gcd(a, k) * gcd(b, k)) % k == 0. This allows us to only care about the GCD of each number with k, significantly reducing the problem's complexity since the number of divisors of k is much smaller than k itself.
This approach processes the array element by element. It maintains a frequency map of the GCDs of numbers encountered so far. For each new number, it checks against the stored GCDs to count new valid pairs.
Algorithm
- Initialize a counter
countto 0 and a hash mapgcdFreqto store the frequency of GCDs of numbers seen so far. - Iterate through each number
numin thenumsarray. - For each
num, calculateg1 = gcd(num, k). - Now, iterate through the existing entries
(g2, freq)in thegcdFreqmap.g2is the GCD of a previously seen number, andfreqis how many times it has appeared. - For each
g2, check if the product of the GCDs is divisible byk:(long)g1 * g2 % k == 0. - If it is, it means the current
numforms a valid pair with allfreqprevious numbers corresponding tog2. Addfreqto the totalcount. - After checking against all previous GCDs, update the frequency map for the current number's GCD:
gcdFreq.put(g1, gcdFreq.getOrDefault(g1, 0) + 1). - Return the total
count.
Walkthrough
We iterate through the nums array, and for each element num, we calculate its GCD with k, let's call it g1. We then need to find how many previously seen numbers prev_num can form a valid pair with num. Based on the property (num * prev_num) % k == 0 <=> (gcd(num, k) * gcd(prev_num, k)) % k == 0, we can check this condition using the GCDs.
We maintain a frequency map, gcdFreq, where keys are GCDs and values are their counts. For the current g1, we iterate through all keys g2 in gcdFreq. If (g1 * g2) % k == 0, we add the frequency of g2 to our total pair count. Finally, we update the frequency of g1 in the map to include the current number for subsequent calculations.
import java.util.HashMap;import java.util.Map; class Solution { public long countPairs(int[] nums, int k) { long count = 0; Map<Integer, Integer> gcdFreq = new HashMap<>(); for (int num : nums) { int g1 = gcd(num, k); for (Map.Entry<Integer, Integer> entry : gcdFreq.entrySet()) { int g2 = entry.getKey(); int freq = entry.getValue(); if (((long) g1 * g2) % k == 0) { count += freq; } } gcdFreq.put(g1, gcdFreq.getOrDefault(g1, 0) + 1); } return count; } private int gcd(int a, int b) { while (b != 0) { int temp = b; b = a % b; a = temp; } return a; }}Complexity
Time
O(N * d(k)) - Where N is the length of `nums` and `d(k)` is the number of divisors of `k`. The outer loop runs N times, and the inner loop iterates through the keys of the map, which has at most `d(k)` entries. The GCD calculation takes O(log k). So, a more precise complexity is O(N * (log k + d(k))).
Space
O(d(k)) - Where `d(k)` is the number of divisors of `k`. The space is used for the `gcdFreq` map, which stores at most `d(k)` unique keys.
Trade-offs
Pros
Significantly more efficient than the brute-force approach.
Correctly handles large inputs within the time limit.
Cons
While much better than brute force, it can be slightly less efficient than the final approach because for each element, it iterates through all unique GCDs found so far.
This is the most efficient approach. It refines the GCD-based method by separating the process into two main steps. First, it aggregates the frequencies of all gcd(num, k) values from the input array. Second, it uses this frequency map to calculate the total number of valid pairs directly, without iterating through the original array again for pair-wise checks. This avoids redundant computations found in the previous approach.
Algorithm
- Step 1: Compute GCD Frequencies
- Initialize a hash map
gcdFreq. - Iterate through each
numin thenumsarray. - Calculate
g = gcd(num, k). - Update the frequency of
gin the map:gcdFreq.put(g, gcdFreq.getOrDefault(g, 0) + 1).
- Initialize a hash map
- Step 2: Count Pairs from Frequencies
- Initialize
count = 0. - Create a list
divisorscontaining the unique GCDs (the keys fromgcdFreq). - Use a nested loop to iterate through all pairs of unique GCDs
(d1, d2)from thedivisorslist, with the inner loop starting from the outer loop's index to avoid duplicate pairs and handle same-element pairs correctly. - For each pair
(d1, d2), check if(long)d1 * d2 % k == 0. - If the condition is met:
- If
d1 == d2, it means we are forming pairs from numbers that have the same GCD. If there arensuch numbers, they formn * (n - 1) / 2pairs. Add this tocount. - If
d1 != d2, we are forming pairs between two different groups of numbers. If there aren1numbers with GCDd1andn2with GCDd2, they formn1 * n2pairs. Add this tocount.
- If
- Initialize
- Return the total
count.
Walkthrough
The logic remains centered on the property that (a * b) % k == 0 is equivalent to (gcd(a, k) * gcd(b, k)) % k == 0.
First, we pass through the nums array once to build a frequency map gcdFreq of all gcd(num, k) values. This takes O(N log k) time.
Once we have this map, the problem is transformed into a combinatorial one: given a multiset of GCDs, how many pairs (g1, g2) satisfy (g1 * g2) % k == 0? We can solve this by iterating through the unique GCDs. For every pair of unique GCDs (d1, d2), we check the divisibility condition. If it holds, we calculate how many pairs from the original nums array this corresponds to using the frequencies stored in gcdFreq and add it to our total count. This counting part has a complexity of O(d(k)<sup>2</sup>), where d(k) is the number of divisors of k.
import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map; class Solution { public long countPairs(int[] nums, int k) { Map<Integer, Integer> gcdFreq = new HashMap<>(); for (int num : nums) { int g = gcd(num, k); gcdFreq.put(g, gcdFreq.getOrDefault(g, 0) + 1); } long count = 0; List<Integer> divisors = new ArrayList<>(gcdFreq.keySet()); for (int i = 0; i < divisors.size(); i++) { for (int j = i; j < divisors.size(); j++) { int d1 = divisors.get(i); int d2 = divisors.get(j); if (((long) d1 * d2) % k == 0) { if (d1 == d2) { long n = gcdFreq.get(d1); count += n * (n - 1) / 2; } else { long n1 = gcdFreq.get(d1); long n2 = gcdFreq.get(d2); count += n1 * n2; } } } } return count; } private int gcd(int a, int b) { while (b != 0) { int temp = b; b = a % b; a = temp; } return a; }}Complexity
Time
O(N * log(k) + d(k)<sup>2</sup>) - The first step of building the frequency map takes O(N * log(k)). The second step of counting pairs takes O(d(k)<sup>2</sup>). Since N is much larger than d(k)<sup>2</sup> in the worst case, the overall complexity is dominated by the first step.
Space
O(d(k)) - Where `d(k)` is the number of divisors of `k`. Space is needed for the frequency map and the list of unique GCDs.
Trade-offs
Pros
This is the most time-efficient solution for the given constraints.
It cleanly separates the logic of data processing and combinatorial counting.
Cons
The implementation is slightly more complex as it involves two distinct phases: data aggregation and pair counting.
Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.