Facebook Pixel

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:

  1. 0 <= i < j < n (i must be less than j)
  2. nums[i] == nums[j] (the values at positions i and j must be equal)
  3. (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 and 0 * 6 = 0 which is divisible by 2 ✓
  • Pair (2, 3): nums[2] = nums[3] = 2 and 2 * 3 = 6 which is divisible by 2 ✓
  • Pair (1, 5): nums[1] = nums[5] = 1 and 1 * 5 = 5 which is not divisible by 2 ✗

The function returns the total count of valid pairs.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. The values at the indices must match
  2. 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:

  1. Initialize counter: Start with ans = 0 to keep track of valid pairs.

  2. Outer loop for j: Iterate j from 1 to len(nums) - 1. We start from 1 because we need at least one element before j to form a pair where i < j.

  3. Inner loop for i: For each fixed j, enumerate all possible values of i from 0 to j - 1. The code uses enumerate(nums[:j]) which:

    • Creates a slice of the array from index 0 to j - 1
    • Returns both the index i and the value x at that index
  4. Check conditions: For each pair (i, j), verify:

    • x == nums[j]: Check if the values are equal (where x is nums[i])
    • i * j % k == 0: Check if the product of indices is divisible by k
  5. 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.

  6. 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 Evaluator

Example 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

Step 3: Process j = 2

  • Check all i from 0 to 1:
    • Pair (0, 2): nums[0] = 1, nums[2] = 1 → Equal! Check 0 * 2 = 0, 0 % 4 = 0 → Valid! ans = 1
    • Pair (1, 2): nums[1] = 2, nums[2] = 1 → Different values → Skip

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! Check 1 * 3 = 3, 3 % 4 = 3 → Not divisible → Skip
    • Pair (2, 3): nums[2] = 1, nums[3] = 2 → Different values → Skip

Final Result: 1 valid pair

The only valid pair is (0, 2) because:

  • 0 < 2
  • nums[0] = nums[2] = 1
  • 0 * 2 = 0 and 0 % 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following uses divide and conquer strategy?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More