title: Check If Array Pairs Are Divisible by k
slug: check-if-array-pairs-are-divisible-by-k
difficulty: medium
leetcode_id: 1497
leetcode_url: https://leetcode.com/problems/check-if-array-pairs-are-divisible-by-k/
categories:
  - arrays
  - hash-tables
  - math
patterns:
  - slug: prefix-sum
    is_optimal: true

function_signature: "def can_arrange(arr: list[int], k: int) -> bool:"

test_cases:
  visible:
    - input: { arr: [1, 2, 3, 4, 5, 10, 6, 7, 8, 9], k: 5 }
      expected: true
    - input: { arr: [1, 2, 3, 4, 5, 6], k: 7 }
      expected: true
    - input: { arr: [1, 2, 3, 4, 5, 6], k: 10 }
      expected: false
  hidden:
    - input: { arr: [-1, 1, -2, 2, -3, 3], k: 3 }
      expected: true
    - input: { arr: [0, 0, 0, 0], k: 5 }
      expected: true
    - input: { arr: [5, 5], k: 5 }
      expected: true
    - input: { arr: [1, 2], k: 4 }
      expected: false
    - input: { arr: [-10, 10], k: 2 }
      expected: true
    - input: { arr: [3, 3, 3, 3, 3, 3], k: 3 }
      expected: true

description: |
  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*.

constraints: |
  - `arr.length == n`
  - `1 <= n <= 10^5`
  - `n` is even
  - `-10^9 <= arr[i] <= 10^9`
  - `1 <= k <= 10^5`

examples:
  - 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). Each pair sums to a multiple of 5."
  - input: "arr = [1,2,3,4,5,6], k = 7"
    output: "true"
    explanation: "Pairs are (1,6), (2,5) and (3,4). Each pair sums to 7, which is divisible by 7."
  - input: "arr = [1,2,3,4,5,6], k = 10"
    output: "false"
    explanation: "No way to divide arr into 3 pairs where each sum is divisible by 10."

explanation:
  intuition: |
    Think about what it means for two numbers to sum to a multiple of `k`. If `a + b` is divisible by `k`, then `(a % k) + (b % k)` must equal either `0` or `k`.

    Imagine you have buckets numbered `0` to `k-1`, where each bucket holds numbers with that remainder when divided by `k`. For two numbers to pair together, their remainders must "complete" each other to reach `k` (or both be zero).

    For example, with `k = 5`:
    - A number with remainder `1` must pair with a number with remainder `4` (since `1 + 4 = 5`)
    - A number with remainder `2` must pair with a number with remainder `3`
    - A number with remainder `0` must pair with another number with remainder `0`

    This insight transforms the problem: instead of trying to find actual pairs, we just need to **count remainders** and check if complementary buckets have equal counts.

  approach: |
    We solve this using a **Remainder Counting Approach**:

    **Step 1: Build a remainder frequency map**

    - Create a hash map (or array of size `k`) to count how many numbers have each remainder
    - For each number in `arr`, compute `remainder = num % k`
    - **Handle negative numbers**: In Python, `(-3) % 5 = 2`, but in some languages it returns `-3`. To ensure a positive remainder, use `((num % k) + k) % k`
    - Increment the count for that remainder

    &nbsp;

    **Step 2: Check remainder 0 separately**

    - Numbers with remainder `0` can only pair with other numbers that have remainder `0`
    - Their count must be **even** (so they can all pair up)
    - If `count[0]` is odd, return `false`

    &nbsp;

    **Step 3: Check complementary pairs**

    - For each remainder `r` from `1` to `k/2`, check that `count[r] == count[k - r]`
    - These are complementary remainders that must pair together
    - If any pair has unequal counts, return `false`

    &nbsp;

    **Step 4: Handle the middle remainder (when k is even)**

    - If `k` is even, remainder `k/2` pairs with itself (since `k/2 + k/2 = k`)
    - Similar to remainder `0`, the count of `k/2` must be even
    - This is automatically handled in step 3 when `r == k - r`

    &nbsp;

    **Step 5: Return true**

    - If all checks pass, we can form valid pairs

  common_pitfalls:
    - title: Forgetting Negative Numbers
      description: |
        The array can contain negative numbers (up to `-10^9`). The modulo operation behaves differently across languages for negative numbers.

        In Python: `(-3) % 5 = 2` (correct for this problem)
        In Java/C++: `(-3) % 5 = -3` (problematic!)

        For languages where `%` can return negative values, convert to positive: `((num % k) + k) % k`. This ensures all remainders fall in the range `[0, k-1]`.
      wrong_approach: "Using num % k directly without handling negatives"
      correct_approach: "Use ((num % k) + k) % k for positive remainders"

    - title: Brute Force Pairing
      description: |
        Trying to actually find and match pairs using nested loops results in **O(n^2)** complexity. With `n = 10^5`, this means up to 10 billion operations, causing TLE.

        The key insight is that we don't need to find the actual pairs — we only need to verify that valid pairings *exist* by checking remainder counts.
      wrong_approach: "Nested loops trying every possible pairing"
      correct_approach: "Count remainders and check complementary counts"

    - title: Not Handling Remainder 0 Specially
      description: |
        Numbers with remainder `0` must pair with each other (no other remainder complements them to `k`). If there's an odd count of such numbers, pairing is impossible.

        Similarly, when `k` is even, numbers with remainder `k/2` must pair with each other.
      wrong_approach: "Only checking complementary pairs without special case for 0"
      correct_approach: "Check that count[0] is even before checking other pairs"

  key_takeaways:
    - "**Modular arithmetic insight**: Two numbers sum to a multiple of `k` when their remainders sum to `0` or `k`"
    - "**Count instead of pair**: When checking if valid pairings exist, counting elements in each category is often enough"
    - "**Handle edge cases**: Negative modulo and self-pairing remainders (0 and k/2) need special attention"
    - "**Pattern recognition**: This transforms a pairing problem into a counting/frequency problem"

  time_complexity: "O(n). We iterate through the array once to build the frequency map, then iterate through at most `k` remainders to verify pairing is possible."
  space_complexity: "O(k). We store counts for up to `k` different remainders in our frequency map."

solutions:
  - approach_name: Remainder Counting
    is_optimal: true
    code: |
      def can_arrange(arr: list[int], k: int) -> bool:
          # Count frequency of each remainder
          remainder_count = [0] * k

          for num in arr:
              # Handle negative numbers with proper modulo
              remainder = ((num % k) + k) % k
              remainder_count[remainder] += 1

          # Check remainder 0: must have even count (pairs with itself)
          if remainder_count[0] % 2 != 0:
              return False

          # Check complementary remainders
          for r in range(1, k // 2 + 1):
              if r == k - r:
                  # Middle remainder (k/2 when k is even): must be even
                  if remainder_count[r] % 2 != 0:
                      return False
              else:
                  # Complementary remainders must have equal counts
                  if remainder_count[r] != remainder_count[k - r]:
                      return False

          return True
    explanation: |
      **Time Complexity:** O(n) — Single pass through array plus O(k) remainder checks.

      **Space Complexity:** O(k) — Array to store remainder counts.

      We count how many numbers fall into each remainder bucket, then verify that complementary remainders have matching counts. This avoids the need to actually find pairs.

  - approach_name: Hash Map Approach
    is_optimal: true
    code: |
      from collections import Counter

      def can_arrange(arr: list[int], k: int) -> bool:
          # Count remainders using Counter
          remainder_count = Counter()

          for num in arr:
              remainder = ((num % k) + k) % k
              remainder_count[remainder] += 1

          # Verify pairing is possible
          for r in remainder_count:
              complement = (k - r) % k  # Handles r=0 case elegantly

              if remainder_count[r] != remainder_count[complement]:
                  return False

          return True
    explanation: |
      **Time Complexity:** O(n) — Single pass to count, plus iteration over unique remainders.

      **Space Complexity:** O(min(n, k)) — Hash map only stores remainders that actually appear.

      This variant uses a hash map instead of a fixed-size array. The `(k - r) % k` formula elegantly handles the remainder 0 case (since `(k - 0) % k = 0`). For sparse arrays where most remainders don't appear, this uses less memory than the array approach.

  - approach_name: Brute Force
    is_optimal: false
    code: |
      def can_arrange(arr: list[int], k: int) -> bool:
          n = len(arr)
          used = [False] * n

          def backtrack(pairs_formed: int) -> bool:
              # All pairs formed successfully
              if pairs_formed == n // 2:
                  return True

              # Find first unused element
              first = -1
              for i in range(n):
                  if not used[i]:
                      first = i
                      break

              # Try pairing with every other unused element
              used[first] = True
              for j in range(first + 1, n):
                  if not used[j] and (arr[first] + arr[j]) % k == 0:
                      used[j] = True
                      if backtrack(pairs_formed + 1):
                          return True
                      used[j] = False

              used[first] = False
              return False

          return backtrack(0)
    explanation: |
      **Time Complexity:** O(n!! ) — Double factorial due to pairing permutations.

      **Space Complexity:** O(n) — Recursion stack and used array.

      This backtracking approach tries all possible pairings. While correct, it's exponentially slow and will TLE on large inputs. Included to illustrate why the counting approach is necessary.
