Check If Array Pairs Are Divisible by k
MedPrompt
Given an array of integers arr of even length n and an integer k.
We want to divide the array into exactly n / 2 pairs such that the sum of each pair is divisible by k.
Return true If you can find a way to do that or false otherwise.
Example 1:
Input: arr = [1,2,3,4,5,10,6,7,8,9], k = 5
Output: true
Explanation: Pairs are (1,9),(2,8),(3,7),(4,6) and (5,10).Example 2:
Input: arr = [1,2,3,4,5,6], k = 7
Output: true
Explanation: Pairs are (1,6),(2,5) and(3,4).Example 3:
Input: arr = [1,2,3,4,5,6], k = 10
Output: false
Explanation: You can try all possible pairs to see that there is no way to divide arr into 3 pairs each with sum divisible by 10.
Constraints:
arr.length == n1 <= n <= 105nis even.-109 <= arr[i] <= 1091 <= k <= 105
Approaches
2 approaches with complexity analysis and trade-offs.
A much more efficient and optimal approach leverages modular arithmetic. The key insight is that for a pair (a, b) to have a sum divisible by k, their remainders must complement each other. That is, (a % k + b % k) % k == 0. This implies that for an element with remainder r, its partner must have a remainder of k - r (or 0 if r is 0). Instead of trying to pair actual numbers, we can just count the frequencies of their remainders and check if these counts allow for a valid pairing.
Algorithm
- Create an integer array
remainderCountsof sizek, initialized to all zeros. This array will store the frequency of each possible remainder. - Iterate through each number
numin the input arrayarr. - For each
num, calculate its remainder modulok. To handle negative numbers correctly and ensure the remainder is in the range[0, k-1], use the formularem = ((num % k) + k) % k. - Increment the count for this remainder:
remainderCounts[rem]++. - After populating the frequency array, check the pairing conditions:
- Check
remainderCounts[0]. Since elements with a remainder of 0 can only be paired with other elements with a remainder of 0, their total count must be even. IfremainderCounts[0] % 2 != 0, returnfalse. - Iterate from
r = 1up tok / 2. - For each
r, ifr * 2 == k(which only happens ifkis even andr = k/2), these elements must pair with each other. Thus,remainderCounts[r]must be even. If not, returnfalse. - Otherwise (if
r != k-r), an element with remainderrmust pair with an element with remainderk-r. Therefore, their counts must be equal. IfremainderCounts[r] != remainderCounts[k - r], returnfalse.
- Check
- If all conditions are met after checking all remainders, it means a valid pairing is possible. Return
true.
Walkthrough
This method transforms the problem from pairing numbers to pairing remainders. We don't care about the numbers themselves, only their remainders when divided by k.
The algorithm proceeds in two main phases:
-
Counting Remainders: We iterate through the entire input array
arronce. For each number, we compute its remainder modulok. A crucial detail is handling potentially negative numbers inarr. The standard%operator in Java can yield negative results (e.g.,-1 % 5is-1). To map all remainders to the non-negative range[0, k-1], we use the expression((num % k) + k) % k. We use an array of sizekto store the frequency of each of these non-negative remainders. -
Verifying Frequencies: After counting, we check if the frequencies allow for perfect pairing.
- Remainder 0: An element
xwithx % k == 0needs a partnerywithy % k == 0. This means all elements divisible bykmust be paired amongst themselves. Therefore, the total count of such elements (remainderCounts[0]) must be even. - Complementary Remainders: An element
xwith remainderr(wherer != 0) needs a partnerywith remainderk - r. This means the number of elements with remainderrmust exactly match the number of elements with remainderk - r. - Special Case
r = k/2: Ifkis an even number, a special case arises for the remainderr = k / 2. Here,k - r = r, so an element with this remainder must be paired with another element that also has the same remainder. Consequently, the count of elements with remainderk/2must be even.
- Remainder 0: An element
If all these conditions hold, we can form the pairs, so we return true. Otherwise, it's impossible, and we return false.
class Solution { public boolean canArrange(int[] arr, int k) { int[] remainderCounts = new int[k]; for (int num : arr) { int rem = ((num % k) + k) % k; remainderCounts[rem]++; } // Condition 1: Count of numbers with remainder 0 must be even. if (remainderCounts[0] % 2 != 0) { return false; } // Condition 2: For r from 1 to k/2, check complementary pairs. for (int r = 1; r <= k / 2; r++) { // If r is its own complement (only when k is even and r = k/2) if (r * 2 == k) { if (remainderCounts[r] % 2 != 0) { return false; } } else { // For all other r, count(r) must equal count(k-r). if (remainderCounts[r] != remainderCounts[k - r]) { return false; } } } return true; }}Complexity
Time
O(n + k). We iterate through the input array of size `n` once to build the frequency map, and then we iterate through the frequency array of size `k` (or `k/2`) once to check the conditions.
Space
O(k), as we need an auxiliary array of size `k` to store the frequencies of the remainders.
Trade-offs
Pros
Highly efficient with linear time complexity.
Optimal solution for the given constraints.
Handles all edge cases, including negative numbers, correctly and elegantly.
Cons
Requires extra space proportional to
k, which could be large ifkis very large (though it's fine for the given constraints).
This approach attempts to solve the problem by exploring every possible way to form pairs from the array elements. It uses a classic backtracking algorithm to try all combinations. If it finds any single valid partition of the array into n/2 pairs where each pair's sum is divisible by k, it concludes that it's possible and returns true.
Algorithm
- Initialize a boolean array
usedof sizentofalseto track which elements have been paired. - Define a recursive helper function, for example,
findPairs(arr, k, used, pairsFormed). - Base Case: If
pairsFormedequalsn / 2, it means all elements have been successfully paired. Returntrue. - Recursive Step:
- Find the first element
arr[i]that has not been used yet (used[i]isfalse). - Mark
arr[i]as used (used[i] = true). - Iterate through the subsequent elements
arr[j](wherej > i). - If
arr[j]is not used and(arr[i] + arr[j]) % k == 0, we have found a potential pair. - Mark
arr[j]as used and make a recursive callfindPairs(arr, k, used, pairsFormed + 1). - If the recursive call returns
true, it means a full valid partition was found, so we returntrueup the call stack. - If the call returns
false, we must backtrack. Unmarkarr[j]as used (used[j] = false) and continue searching for another partner forarr[i].
- Find the first element
- If the loop finishes without finding a suitable partner for
arr[i], it's impossible to form a valid partition from this state. We backtrack by unmarkingarr[i]and returnfalse.
Walkthrough
The core idea is to build the pairs one by one. We use a boolean array used to keep track of elements that have already been included in a pair.
A recursive function, say findPairs, attempts to form the remaining pairs. The function finds the first available element arr[i] and tries to pair it with every other available element arr[j]. If (arr[i] + arr[j]) % k == 0, we tentatively form this pair, mark both elements as used, and then recursively call the function to form the next pair from the remaining elements.
If a recursive call eventually leads to a state where all n/2 pairs are formed, we have found a solution. If a path leads to a dead end (e.g., an element cannot be paired with any remaining element), we "backtrack" by undoing the last pair formation and trying a different option.
class Solution { public boolean canArrange(int[] arr, int k) { boolean[] used = new boolean[arr.length]; return findPairs(arr, k, used, 0); } private boolean findPairs(int[] arr, int k, boolean[] used, int pairsFormed) { if (pairsFormed == arr.length / 2) { return true; } // Find the first unused element's index int i = -1; for (int idx = 0; idx < arr.length; idx++) { if (!used[idx]) { i = idx; break; } } if (i == -1) { // Should not be reached if pairsFormed < arr.length / 2 return true; } used[i] = true; // Try to find a pair for arr[i] for (int j = i + 1; j < arr.length; j++) { if (!used[j]) { // Note: The problem statement guarantees arr[i] can be negative. // A simple (a + b) % k == 0 works for both positive and negative numbers in Java // as long as we are just checking for a zero result. if ((arr[i] + arr[j]) % k == 0) { used[j] = true; if (findPairs(arr, k, used, pairsFormed + 1)) { return true; } used[j] = false; // Backtrack } } } used[i] = false; // Backtrack return false; }}Complexity
Time
O((n-1)!!) which is approximately O((n/2)!). This is factorial-level complexity, arising from the number of ways to partition `n` items into `n/2` pairs. It is computationally infeasible for the given constraints.
Space
O(n) for the recursion stack depth and the `used` boolean array.
Trade-offs
Pros
Conceptually straightforward as it directly models the problem of trying all pairings.
Cons
Extremely inefficient and slow.
Guaranteed to cause a 'Time Limit Exceeded' (TLE) error for the constraints specified in the problem (n up to 10^5).
Not a practical solution for anything beyond very small input sizes.
Solutions
Solution
class Solution {public boolean canArrange(int[] arr, int k) { int[] cnt = new int[k]; for (int x : arr) { ++cnt[(x % k + k) % k]; } for (int i = 1; i < k; ++i) { if (cnt[i] != cnt[k - i]) { return false; } } return cnt[0] % 2 == 0; }}function canArrange ( arr , k ) { const cnt = Array ( k ). fill ( 0 ); for ( const x of arr ) { cnt [(( x % k ) + k ) % k ] ++ ; } for ( let i = 1 ; i < k ; i ++ ) { if ( cnt [ i ] !== cnt [ k - i ]) return false ; } return cnt [ 0 ] % 2 === 0 ; }class Solution {public: bool canArrange(vector<int> &arr, int k) { vector<int> cnt(k); for (int &x : arr) { ++cnt[((x % k) + k) % k]; } for (int i = 1; i < k; ++i) { if (cnt[i] != cnt[k - i]) { return false; } } return cnt[0] % 2 == 0; }};class Solution: def canArrange(self, arr: List[int], k: int) -> bool: cnt = Counter(x % k for x in arr) return cnt[0] % 2 == 0 and all(cnt[i] == cnt[k - i] for i in range(1, k))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.