1497. Check If Array Pairs Are Divisible by k
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 byk
, it needs to pair with a number having remainderk-r
(or remainder0
pairs with another remainder0
) - The solution uses a frequency counter to track remainders and verifies that each remainder has a matching complement with the same frequency
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
andb % k
equal0
, 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 remainder4
(since1 + 4 = 5
) - A number with remainder
2
needs to pair with remainder3
(since2 + 3 = 5
) - A number with remainder
0
needs to pair with another remainder0
This leads us to count the frequency of each remainder value. For a valid pairing to exist:
- The count of numbers with remainder
0
must be even (they pair among themselves) - For each remainder
i
from1
tok-1
, the count of numbers with remainderi
must equal the count of numbers with remainderk-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:
-
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. -
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. -
Check complementary remainders: For each remainder
i
from 1 to k-1, we verify thatcnt[i] == cnt[k - i]
. This ensures every element with remainderi
has a matching partner with remainderk - 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
andk-i
have equal counts, we can pair all elements with remainderi
with elements having remainderk-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 fork-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 EvaluatorExample 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 ifcnt[1] == cnt[6-1]
→cnt[1] == cnt[5]
→1 == 1
✓i = 2
: Check ifcnt[2] == cnt[6-2]
→cnt[2] == cnt[4]
→0 == 0
✓i = 3
: Check ifcnt[3] == cnt[6-3]
→cnt[3] == cnt[3]
→2 == 2
✓i = 4
: Check ifcnt[4] == cnt[6-4]
→cnt[4] == cnt[2]
→0 == 0
✓i = 5
: Check ifcnt[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 computingx % k
for each element:O(n)
- The
all()
function with the generator expression iterates through values from1
tok-1
, checking the conditioncnt[i] == cnt[k - i]
for eachi
: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 mostk
distinct keys (remainders from0
tok-1
), since any number modulok
can only produce values in the range[0, k-1]
- The generator expression in
all()
doesn't create additional space beyondO(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]
andk = 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]
andk = 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 whenk
is even - Optimizes loop range to avoid redundant checks
- Relies on Python's modulo behavior for negative numbers
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
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!