2176. Count Equal and Divisible Pairs in an Array
Problem Description
You are given an array of integers nums
(0-indexed) with length n
and an integer k
. Your task is to find the number of pairs (i, j)
that satisfy all of the following conditions:
0 <= i < j < n
(i must be less than j)nums[i] == nums[j]
(the values at positions i and j must be equal)(i * j) % k == 0
(the product of indices i and j must be divisible by k)
The solution uses a nested loop approach. The outer loop iterates through each possible value of j
from 1 to n-1
. For each j
, the inner loop checks all possible values of i
from 0 to j-1
. For each pair (i, j)
, it checks if the elements at these positions are equal (nums[i] == nums[j]
) and if their index product is divisible by k
(i * j % k == 0
). If both conditions are met, the pair is counted.
For example, if nums = [3, 1, 2, 2, 2, 1, 3]
and k = 2
:
- Pair
(0, 6)
:nums[0] = nums[6] = 3
and0 * 6 = 0
which is divisible by 2 ✓ - Pair
(2, 3)
:nums[2] = nums[3] = 2
and2 * 3 = 6
which is divisible by 2 ✓ - Pair
(1, 5)
:nums[1] = nums[5] = 1
and1 * 5 = 5
which is not divisible by 2 ✗
The function returns the total count of valid pairs.
Intuition
The problem asks us to find pairs of indices where the elements are equal and the product of indices is divisible by k
. Since we need to check every possible pair (i, j)
where i < j
, the most straightforward approach is to examine all such pairs.
Why does brute force make sense here? We need to verify two conditions for each pair:
- The values at the indices must match
- The product of the indices must be divisible by
k
There's no apparent pattern or mathematical property that would allow us to skip checking certain pairs without examining them first. The divisibility condition (i * j) % k == 0
depends on both indices, and we can't predict which pairs will satisfy this without computing the product.
The natural way to generate all pairs (i, j)
where i < j
is through nested loops. We fix j
as the second index and iterate through all possible values of i
that are less than j
. This ensures we never count the same pair twice and always maintain i < j
.
For each pair, we perform a simple check: are the values equal AND is their index product divisible by k
? If yes, we increment our counter. The elegance of this solution lies in its directness - we're literally counting exactly what the problem asks for without any unnecessary complexity.
The time complexity of O(n²)
is acceptable for this problem since we must potentially examine all pairs, and there's no obvious way to eliminate candidates without checking them.
Solution Approach
The implementation uses a nested loop structure to enumerate all valid pairs (i, j)
where i < j
.
Step-by-step breakdown:
-
Initialize counter: Start with
ans = 0
to keep track of valid pairs. -
Outer loop for j: Iterate
j
from 1 tolen(nums) - 1
. We start from 1 because we need at least one element beforej
to form a pair wherei < j
. -
Inner loop for i: For each fixed
j
, enumerate all possible values ofi
from 0 toj - 1
. The code usesenumerate(nums[:j])
which:- Creates a slice of the array from index 0 to
j - 1
- Returns both the index
i
and the valuex
at that index
- Creates a slice of the array from index 0 to
-
Check conditions: For each pair
(i, j)
, verify:x == nums[j]
: Check if the values are equal (wherex
isnums[i]
)i * j % k == 0
: Check if the product of indices is divisible byk
-
Count valid pairs: The expression
int(x == nums[j] and i * j % k == 0)
evaluates to:- 1 if both conditions are true
- 0 if either condition is false
This clever use of
int()
on a boolean expression allows us to add 1 to our counter only when we find a valid pair. -
Return result: After examining all possible pairs, return the total count.
The algorithm systematically checks every pair exactly once, ensuring we don't miss any valid pairs or count duplicates. The time complexity is O(n²)
since we examine roughly n * (n-1) / 2
pairs, and the space complexity is O(1)
as we only use a counter variable.
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 with nums = [1, 2, 1, 2]
and k = 4
.
We need to find pairs (i, j)
where i < j
, nums[i] == nums[j]
, and (i * j) % k == 0
.
Step 1: Initialize
ans = 0
(our counter for valid pairs)
Step 2: Process j = 1
- Check all i from 0 to 0:
- Pair (0, 1):
nums[0] = 1
,nums[1] = 2
→ Different values → Skip - No valid pairs found
- Pair (0, 1):
Step 3: Process j = 2
- Check all i from 0 to 1:
- Pair (0, 2):
nums[0] = 1
,nums[2] = 1
→ Equal! Check0 * 2 = 0
,0 % 4 = 0
→ Valid!ans = 1
- Pair (1, 2):
nums[1] = 2
,nums[2] = 1
→ Different values → Skip
- Pair (0, 2):
Step 4: Process j = 3
- Check all i from 0 to 2:
- Pair (0, 3):
nums[0] = 1
,nums[3] = 2
→ Different values → Skip - Pair (1, 3):
nums[1] = 2
,nums[3] = 2
→ Equal! Check1 * 3 = 3
,3 % 4 = 3
→ Not divisible → Skip - Pair (2, 3):
nums[2] = 1
,nums[3] = 2
→ Different values → Skip
- Pair (0, 3):
Final Result: 1 valid pair
The only valid pair is (0, 2) because:
0 < 2
✓nums[0] = nums[2] = 1
✓0 * 2 = 0
and0 % 4 = 0
✓
Solution Implementation
1class Solution:
2 def countPairs(self, nums: List[int], k: int) -> int:
3 """
4 Count pairs (i, j) where i < j, nums[i] == nums[j], and (i * j) is divisible by k.
5
6 Args:
7 nums: List of integers
8 k: Integer divisor to check if i * j is divisible by
9
10 Returns:
11 Number of valid pairs satisfying all conditions
12 """
13 count = 0
14
15 # Iterate through all possible j values (second element of pair)
16 for j in range(1, len(nums)):
17 # Check all possible i values (first element of pair, where i < j)
18 for i in range(j):
19 # Check if values are equal and product of indices is divisible by k
20 if nums[i] == nums[j] and (i * j) % k == 0:
21 count += 1
22
23 return count
24
1class Solution {
2 /**
3 * Counts the number of pairs (i, j) where i < j, nums[i] == nums[j],
4 * and (i * j) is divisible by k.
5 *
6 * @param nums the input array of integers
7 * @param k the divisor to check if i * j is divisible by
8 * @return the count of valid pairs
9 */
10 public int countPairs(int[] nums, int k) {
11 int pairCount = 0;
12
13 // Iterate through all possible pairs where j > i
14 for (int j = 1; j < nums.length; j++) {
15 for (int i = 0; i < j; i++) {
16 // Check if values are equal and product of indices is divisible by k
17 if (nums[i] == nums[j] && (i * j) % k == 0) {
18 pairCount++;
19 }
20 }
21 }
22
23 return pairCount;
24 }
25}
26
1class Solution {
2public:
3 int countPairs(vector<int>& nums, int k) {
4 int pairCount = 0;
5
6 // Iterate through all possible pairs (i, j) where i < j
7 for (int j = 1; j < nums.size(); ++j) {
8 for (int i = 0; i < j; ++i) {
9 // Check if values at indices i and j are equal
10 // AND if the product of indices is divisible by k
11 if (nums[i] == nums[j] && (i * j) % k == 0) {
12 pairCount++;
13 }
14 }
15 }
16
17 return pairCount;
18 }
19};
20
1/**
2 * Counts the number of pairs (i, j) where i < j, nums[i] == nums[j],
3 * and (i * j) is divisible by k
4 * @param nums - Array of numbers to check for pairs
5 * @param k - The divisor to check if (i * j) is divisible by
6 * @returns The count of valid pairs
7 */
8function countPairs(nums: number[], k: number): number {
9 // Initialize counter for valid pairs
10 let pairCount: number = 0;
11
12 // Iterate through all possible pairs where i < j
13 // j starts from index 1 to ensure i < j
14 for (let j: number = 1; j < nums.length; j++) {
15 // For each j, check all possible i values where i < j
16 for (let i: number = 0; i < j; i++) {
17 // Check if values are equal and product of indices is divisible by k
18 if (nums[i] === nums[j] && (i * j) % k === 0) {
19 pairCount++;
20 }
21 }
22 }
23
24 return pairCount;
25}
26
Time and Space Complexity
The time complexity is O(n^2)
, where n
is the length of the array nums
. This is because we have two nested loops - the outer loop iterates from index 1 to n-1
(which is n-1
iterations), and for each iteration j
, the inner loop iterates through all elements from index 0 to j-1
(which is j
iterations). The total number of operations is 1 + 2 + 3 + ... + (n-1) = n(n-1)/2
, which simplifies to O(n^2)
.
The space complexity is O(1)
. The algorithm only uses a constant amount of extra space for variables ans
, i
, j
, and x
, regardless of the input size. The slicing operation nums[:j]
creates an iterator in the enumerate function but doesn't create a new list in memory due to Python's optimization, and even if it did create a temporary slice, it would be reused in each iteration rather than accumulating space.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Index Confusion: Using Values Instead of Indices
A frequent mistake is checking if the product of the values is divisible by k instead of the product of the indices.
Incorrect:
if nums[i] == nums[j] and (nums[i] * nums[j]) % k == 0: # Wrong!
Correct:
if nums[i] == nums[j] and (i * j) % k == 0: # Right!
2. Edge Case: When i = 0
The product i * j
equals 0 when i = 0
, which is always divisible by any k. This might seem counterintuitive but is mathematically correct. Some might incorrectly try to exclude this case.
Incorrect assumption:
for j in range(1, len(nums)):
for i in range(1, j): # Wrong! Missing i=0 cases
if nums[i] == nums[j] and (i * j) % k == 0:
count += 1
Correct approach:
for j in range(1, len(nums)):
for i in range(j): # Correctly includes i=0
if nums[i] == nums[j] and (i * j) % k == 0:
count += 1
3. Optimization Pitfall: Early Termination
Some might think they can optimize by breaking early when finding matches, but this would miss counting all valid pairs.
Incorrect optimization:
for j in range(1, len(nums)):
for i in range(j):
if nums[i] == nums[j] and (i * j) % k == 0:
count += 1
break # Wrong! This stops checking other valid i values
4. Alternative Solution Using Hash Maps
For better time complexity when k is small, you can optimize by only checking indices where i
is a divisor of k or where gcd(i, k)
divides j
:
def countPairs(self, nums: List[int], k: int) -> int:
from math import gcd
count = 0
n = len(nums)
# Group indices by their values
value_indices = {}
for idx, val in enumerate(nums):
if val not in value_indices:
value_indices[val] = []
value_indices[val].append(idx)
# For each group of same values, check pairs
for indices in value_indices.values():
for j_idx in range(len(indices)):
for i_idx in range(j_idx):
i, j = indices[i_idx], indices[j_idx]
if (i * j) % k == 0:
count += 1
return count
This optimization groups equal values together, potentially reducing comparisons when there are many distinct values in the array.
Which of the following uses divide and conquer strategy?
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!