Facebook Pixel

1497. Check If Array Pairs Are Divisible by k

MediumArrayHash TableCounting
Leetcode Link

Problem Description

You are given an array of integers arr with even length n and an integer k. Your task is to determine if you can divide all elements of the array into exactly n/2 pairs, where each pair's sum is divisible by k.

For example, if you have an array [1, 2, 3, 4, 5, 6] and k = 7, you need to check if you can form 3 pairs from these 6 numbers such that each pair's sum is divisible by 7. One valid pairing would be (1, 6), (2, 5), (3, 4) since 1+6=7, 2+5=7, and 3+4=7 are all divisible by 7.

The function should return true if such a pairing exists, and false otherwise.

The key insight is that for two numbers to sum to a multiple of k, their remainders when divided by k must complement each other. Specifically:

  • If a number has remainder r when divided by k, it needs to pair with a number having remainder k-r (or remainder 0 pairs with another remainder 0)
  • The solution uses a frequency counter to track remainders and verifies that each remainder has a matching complement with the same frequency
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we need to find pairs whose sum is divisible by k, we should think about the remainder (modulo) operation. If two numbers a and b sum to a multiple of k, then (a + b) % k = 0.

This means (a % k + b % k) % k = 0. For this to be true, either:

  • Both a % k and b % k equal 0, or
  • a % k + b % k = k

In other words, if a number has remainder r when divided by k, it must pair with a number having remainder k - r to make their sum divisible by k. The special case is when r = 0, which must pair with another number having remainder 0.

For example, with k = 5:

  • A number with remainder 1 needs to pair with remainder 4 (since 1 + 4 = 5)
  • A number with remainder 2 needs to pair with remainder 3 (since 2 + 3 = 5)
  • A number with remainder 0 needs to pair with another remainder 0

This leads us to count the frequency of each remainder value. For a valid pairing to exist:

  1. The count of numbers with remainder 0 must be even (they pair among themselves)
  2. For each remainder i from 1 to k-1, the count of numbers with remainder i must equal the count of numbers with remainder k-i

By checking these conditions, we can determine if all elements can be paired such that each pair's sum is divisible by k.

Solution Approach

The solution implements the intuition using a frequency counter and validation checks:

  1. Calculate remainders and count frequencies: We use Counter(x % k for x in arr) to create a frequency map where keys are remainders (0 to k-1) and values are their counts in the array.

  2. Check remainder 0: Elements with remainder 0 must pair with each other, so we verify cnt[0] % 2 == 0. If there's an odd count of such elements, pairing is impossible.

  3. Check complementary remainders: For each remainder i from 1 to k-1, we verify that cnt[i] == cnt[k - i]. This ensures every element with remainder i has a matching partner with remainder k - i.

The implementation uses Python's all() function to check if all remainder pairs satisfy the complementary condition. The expression all(cnt[i] == cnt[k - i] for i in range(1, k)) iterates through remainders 1 to k-1 and validates each has the same frequency as its complement.

Why this works:

  • When i and k-i have equal counts, we can pair all elements with remainder i with elements having remainder k-i
  • The range only goes from 1 to k-1 because remainder 0 is handled separately
  • Each complementary pair is effectively checked twice (once for i and once for k-i), but this doesn't affect correctness

The time complexity is O(n + k) where n is the array length (for computing remainders) and k for validation. Space complexity is O(k) for storing the counter.

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 the solution with a concrete example to see how the algorithm works.

Example: arr = [9, 7, 5, 3] and k = 6

Step 1: Calculate remainders

  • 9 % 6 = 3
  • 7 % 6 = 1
  • 5 % 6 = 5
  • 3 % 6 = 3

Step 2: Build frequency counter

cnt = {
    0: 0,  # no elements with remainder 0
    1: 1,  # one element (7) has remainder 1
    3: 2,  # two elements (9, 3) have remainder 3
    5: 1   # one element (5) has remainder 5
}

Step 3: Check remainder 0

  • cnt[0] = 0, which is even ✓ (vacuously true - no elements need pairing)

Step 4: Check complementary remainders For each remainder i from 1 to 5, check if cnt[i] == cnt[k-i]:

  • i = 1: Check if cnt[1] == cnt[6-1]cnt[1] == cnt[5]1 == 1
  • i = 2: Check if cnt[2] == cnt[6-2]cnt[2] == cnt[4]0 == 0
  • i = 3: Check if cnt[3] == cnt[6-3]cnt[3] == cnt[3]2 == 2
  • i = 4: Check if cnt[4] == cnt[6-4]cnt[4] == cnt[2]0 == 0
  • i = 5: Check if cnt[5] == cnt[6-5]cnt[5] == cnt[1]1 == 1

All conditions are satisfied, so the function returns true.

Verification of actual pairing:

  • Pair (7, 5): 7 + 5 = 12, which is divisible by 6 ✓
  • Pair (9, 3): 9 + 3 = 12, which is divisible by 6 ✓

The algorithm correctly identifies that we can form valid pairs where each pair's sum is divisible by k.

Solution Implementation

1class Solution:
2    def canArrange(self, arr: List[int], k: int) -> bool:
3        # Count frequency of each remainder when divided by k
4        # Using modulo k to normalize all values to range [0, k-1]
5        remainder_count = Counter(x % k for x in arr)
6      
7        # Check if pairs can be formed:
8        # 1. Elements with remainder 0 must pair with themselves (need even count)
9        # 2. Elements with remainder i must pair with remainder (k-i)
10      
11        # Check if count of remainder 0 is even (they pair with each other)
12        if remainder_count[0] % 2 != 0:
13            return False
14      
15        # Check if each remainder i has matching count with remainder (k-i)
16        # Only need to check up to k//2 to avoid double checking
17        for i in range(1, (k + 1) // 2):
18            if remainder_count[i] != remainder_count[k - i]:
19                return False
20      
21        # Special case: if k is even, remainder k/2 must pair with itself
22        if k % 2 == 0 and remainder_count[k // 2] % 2 != 0:
23            return False
24      
25        return True
26
1class Solution {
2    public boolean canArrange(int[] arr, int k) {
3        // Create frequency array to count remainders when divided by k
4        int[] remainderCount = new int[k];
5      
6        // Count the frequency of each remainder
7        // Using ((x % k) + k) % k to handle negative numbers correctly
8        for (int num : arr) {
9            int remainder = ((num % k) + k) % k;
10            remainderCount[remainder]++;
11        }
12      
13        // Check if pairs can be formed for remainders 1 to k-1
14        // Elements with remainder i must pair with elements having remainder (k-i)
15        for (int i = 1; i < k; ++i) {
16            if (remainderCount[i] != remainderCount[k - i]) {
17                return false;
18            }
19        }
20      
21        // Check if elements with remainder 0 can form pairs among themselves
22        // They need to be even in count to form pairs
23        return remainderCount[0] % 2 == 0;
24    }
25}
26
1class Solution {
2public:
3    bool canArrange(vector<int>& arr, int k) {
4        // Create a frequency array to count remainders when divided by k
5        // Index represents the remainder, value represents the count
6        vector<int> remainderCount(k);
7      
8        // Count the frequency of each remainder
9        // Handle negative numbers by adding k and taking modulo again
10        for (int& num : arr) {
11            // ((num % k) + k) % k ensures positive remainder for negative numbers
12            // For example: -1 % 5 = -1, but ((-1 % 5) + 5) % 5 = 4
13            int remainder = ((num % k) + k) % k;
14            ++remainderCount[remainder];
15        }
16      
17        // Check if elements with remainder i can pair with elements with remainder k-i
18        for (int i = 1; i < k; ++i) {
19            // For a valid pairing, count of remainder i must equal count of remainder k-i
20            // This ensures each element with remainder i has a pair with remainder k-i
21            if (remainderCount[i] != remainderCount[k - i]) {
22                return false;
23            }
24        }
25      
26        // Special case: elements with remainder 0 must pair among themselves
27        // So their count must be even
28        return remainderCount[0] % 2 == 0;
29    }
30};
31
1/**
2 * Checks if array elements can be arranged into pairs where each pair's sum is divisible by k
3 * @param arr - The input array of integers
4 * @param k - The divisor to check pairs against
5 * @returns true if all elements can form valid pairs, false otherwise
6 */
7function canArrange(arr: number[], k: number): boolean {
8    // Create frequency array to count remainders when divided by k
9    // Index represents remainder value, value represents count of elements with that remainder
10    const remainderCount: number[] = Array(k).fill(0);
11
12    // Count frequency of each remainder
13    // Using ((x % k) + k) % k to handle negative numbers correctly
14    for (const num of arr) {
15        const remainder: number = ((num % k) + k) % k;
16        remainderCount[remainder]++;
17    }
18
19    // Check if elements with remainder i can pair with elements with remainder (k - i)
20    // For a valid pairing: count[i] must equal count[k - i]
21    for (let i = 1; i < k; i++) {
22        if (remainderCount[i] !== remainderCount[k - i]) {
23            return false;
24        }
25    }
26
27    // Special case: elements with remainder 0 must pair with themselves
28    // So their count must be even
29    return remainderCount[0] % 2 === 0;
30}
31

Time and Space Complexity

Time Complexity: O(n + k)

  • Creating the Counter from the list requires iterating through all n elements in the array and computing x % k for each element: O(n)
  • The all() function with the generator expression iterates through values from 1 to k-1, checking the condition cnt[i] == cnt[k - i] for each i: O(k)
  • Each dictionary lookup in the Counter is O(1) on average
  • Total time complexity: O(n + k)

Space Complexity: O(k)

  • The Counter cnt stores at most k distinct keys (remainders from 0 to k-1), since any number modulo k can only produce values in the range [0, k-1]
  • The generator expression in all() doesn't create additional space beyond O(1) for iteration variables
  • Total space complexity: O(k)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Handling Negative Numbers Incorrectly

The most critical pitfall is not properly handling negative numbers when calculating remainders. In Python, the modulo operator % behaves differently with negative numbers compared to some other languages.

Problem Example:

  • For arr = [-1, -1, -2, -2] and k = 3
  • -1 % 3 = 2 in Python (not -1)
  • -2 % 3 = 1 in Python (not -2)

While Python's behavior is actually helpful here (automatically normalizing to positive remainders), developers coming from other languages might add unnecessary correction code like:

remainder = x % k
if remainder < 0:
    remainder += k  # Unnecessary in Python!

2. Not Handling the k/2 Remainder Case When k is Even

When k is even, elements with remainder k/2 must pair with themselves (since k/2 + k/2 = k). The code must check that there's an even count of such elements.

Problem Example:

  • For arr = [2, 6, 10, 14] and k = 8
  • All elements have remainder 2 when divided by 8
  • We need pairs of (2,6) and (10,14), but 2+2 ≠ multiple of 8
  • The issue is remainder 4 (which is k/2) needs special handling

3. Incorrect Loop Range for Validation

Using range(1, k) instead of range(1, (k+1)//2) causes redundant checks and potential issues:

Inefficient approach:

for i in range(1, k):  # Checks each pair twice
    if remainder_count[i] != remainder_count[k - i]:
        return False

Better approach:

for i in range(1, (k + 1) // 2):  # Checks each pair once
    if remainder_count[i] != remainder_count[k - i]:
        return False

4. Using DefaultDict Without Proper Initialization

If using defaultdict(int) instead of Counter, forgetting that unaccessed keys still need checking:

remainder_count = defaultdict(int)
for x in arr:
    remainder_count[x % k] += 1

# Bug: if remainder i doesn't exist, remainder_count[i] = 0
# But remainder_count[k-i] might not be accessed, causing issues

Solution to Avoid These Pitfalls:

class Solution:
    def canArrange(self, arr: List[int], k: int) -> bool:
        # Counter handles missing keys gracefully (returns 0)
        remainder_count = Counter(x % k for x in arr)
      
        # Check remainder 0 (pairs with itself)
        if remainder_count[0] % 2 != 0:
            return False
      
        # Check complementary pairs (only up to k//2 to avoid redundancy)
        for i in range(1, (k + 1) // 2):
            if remainder_count[i] != remainder_count[k - i]:
                return False
      
        # Special case: when k is even, check k/2 remainder
        if k % 2 == 0 and remainder_count[k // 2] % 2 != 0:
            return False
      
        return True

Key improvements:

  • Uses Counter which handles missing keys gracefully
  • Properly handles the k/2 case when k is even
  • Optimizes loop range to avoid redundant checks
  • Relies on Python's modulo behavior for negative numbers
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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
8
1public 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}
12
1class 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}
85

Recommended Readings

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

Load More