title: Array Partition
slug: array-partition
difficulty: easy
leetcode_id: 561
leetcode_url: https://leetcode.com/problems/array-partition/
categories:
  - arrays
  - sorting
patterns:
  - greedy

function_signature: "def array_pair_sum(nums: list[int]) -> int:"

test_cases:
  visible:
    - input: { nums: [1, 4, 3, 2] }
      expected: 4
    - input: { nums: [6, 2, 6, 5, 1, 2] }
      expected: 9
  hidden:
    - input: { nums: [1, 2] }
      expected: 1
    - input: { nums: [1, 1, 1, 1] }
      expected: 2
    - input: { nums: [-1, -2, -3, -4] }
      expected: -6
    - input: { nums: [-5, 5, -3, 3] }
      expected: -2
    - input: { nums: [1, 2, 3, 4, 5, 6] }
      expected: 9

description: |
  Given an integer array `nums` of `2n` integers, group these integers into `n` pairs `(a1, b1), (a2, b2), ..., (an, bn)` such that the sum of `min(ai, bi)` for all `i` is **maximised**.

  Return *the maximised sum*.

constraints: |
  - `1 <= n <= 10^4`
  - `nums.length == 2 * n`
  - `-10^4 <= nums[i] <= 10^4`

examples:
  - input: "nums = [1,4,3,2]"
    output: "4"
    explanation: "All possible pairings are: (1,4),(2,3) → 1+2=3; (1,3),(2,4) → 1+2=3; (1,2),(3,4) → 1+3=4. The maximum possible sum is 4."
  - input: "nums = [6,2,6,5,1,2]"
    output: "9"
    explanation: "The optimal pairing is (1,2), (2,5), (6,6). min(1,2) + min(2,5) + min(6,6) = 1 + 2 + 6 = 9."

explanation:
  intuition: |
    Imagine you have pairs of contestants in a competition where only the weaker member of each pair scores points. Your goal is to maximise the total points scored.

    The key insight is that **pairing wastes the larger element** — whenever you pair two numbers, the larger one contributes nothing to the sum. So the question becomes: how do we minimise this "waste"?

    Think of it like this: if you pair a very small number with a very large number, you're wasting the large number entirely. But if you pair numbers that are close in value, you waste less.

    This leads to the greedy strategy: **sort the array and pair adjacent elements**. When you pair the 1<sup>st</sup> and 2<sup>nd</sup> smallest, the 3<sup>rd</sup> and 4<sup>th</sup> smallest, and so on, you minimise the gap between paired elements. The smaller element in each pair (which we keep) is as large as possible given what's available.

    After sorting `[1, 2, 3, 4]`, pairing `(1,2)` and `(3,4)` gives us `1 + 3 = 4`. The "wasted" values are `2` and `4`, which are the minimum we could waste.

  approach: |
    We solve this using a **Sort and Sum Alternates** approach:

    **Step 1: Sort the array**

    - Sort `nums` in ascending order
    - After sorting, adjacent elements are closest in value

    &nbsp;

    **Step 2: Sum every other element**

    - Take elements at indices `0, 2, 4, ...` (the smaller element of each pair)
    - These are the elements that will contribute to our sum
    - In each pair `(nums[0], nums[1])`, `(nums[2], nums[3])`, etc., the first element is the minimum

    &nbsp;

    **Step 3: Return the sum**

    - The sum of elements at even indices is the maximum possible sum of minimums

    &nbsp;

    This greedy approach works because sorting ensures we pair elements with minimal "waste" — the larger element in each pair is only slightly larger than the smaller one.

  common_pitfalls:
    - title: Trying All Pairings
      description: |
        A brute force approach would try all possible ways to partition `2n` elements into `n` pairs. The number of such partitions is `(2n)! / (2^n * n!)`, which grows extremely fast.

        For `n = 10^4`, this is astronomically large and completely infeasible. Even for small inputs like `n = 10`, there are over 650 million possible pairings.

        The key insight is recognising that we don't need to try all pairings — the greedy approach of pairing sorted adjacent elements is provably optimal.
      wrong_approach: "Enumerate all possible pairings"
      correct_approach: "Sort and pair adjacent elements"

    - title: Pairing Extremes Together
      description: |
        An intuitive but wrong approach might be to pair the smallest with the largest, second smallest with second largest, etc. This feels like it might "balance" things out.

        For `[1, 2, 3, 4]`, pairing `(1,4)` and `(2,3)` gives `min(1,4) + min(2,3) = 1 + 2 = 3`. But pairing adjacents `(1,2)` and `(3,4)` gives `1 + 3 = 4`.

        The problem is that pairing extremes maximises the waste — the large elements contribute nothing, and you're "wasting" more value.
      wrong_approach: "Pair smallest with largest"
      correct_approach: "Pair adjacent elements after sorting"

    - title: Off-by-One with Indices
      description: |
        When summing elements at even indices, ensure you're using `range(0, len(nums), 2)` or equivalent. Starting at index 1 would sum the wrong elements — the maximums of each pair instead of the minimums.

        Alternatively, you can sum with `sum(sorted(nums)[::2])` using Python's slice notation.

  key_takeaways:
    - "**Greedy insight**: When forced to discard one element per pair, minimise loss by keeping discarded values as small as possible relative to what's kept"
    - "**Sorting unlocks structure**: Many pairing/partitioning problems become tractable after sorting reveals the optimal ordering"
    - "**Adjacent pairing pattern**: When elements must be grouped in pairs and order matters, sorting followed by adjacent grouping is a common optimal strategy"
    - "**Avoid enumeration**: Recognise when a greedy or mathematical insight eliminates the need for exhaustive search"

  time_complexity: "O(n log n). The dominant operation is sorting the array of `2n` elements."
  space_complexity: "O(1) to O(n). Depends on the sorting algorithm — in-place sorts like heapsort use O(1), while Python's Timsort uses O(n) auxiliary space."

solutions:
  - approach_name: Sort and Sum Alternates
    is_optimal: true
    code: |
      def array_pair_sum(nums: list[int]) -> int:
          # Sort to pair adjacent elements (minimises waste)
          nums.sort()

          # Sum elements at even indices (smaller of each pair)
          total = 0
          for i in range(0, len(nums), 2):
              total += nums[i]

          return total
    explanation: |
      **Time Complexity:** O(n log n) — Dominated by the sorting step.

      **Space Complexity:** O(1) auxiliary — We sort in-place and use a single variable for the sum.

      After sorting, elements at even indices are the minimum of each adjacent pair. Summing these gives the maximum possible sum.

  - approach_name: Pythonic One-Liner
    is_optimal: true
    code: |
      def array_pair_sum(nums: list[int]) -> int:
          # Sort, then sum every other element starting from index 0
          return sum(sorted(nums)[::2])
    explanation: |
      **Time Complexity:** O(n log n) — Sorting dominates.

      **Space Complexity:** O(n) — `sorted()` creates a new list.

      This concise version uses Python's slice notation `[::2]` to select every other element starting from index 0. Functionally identical to the explicit loop approach.

  - approach_name: Counting Sort (Bounded Range)
    is_optimal: false
    code: |
      def array_pair_sum(nums: list[int]) -> int:
          # Use counting sort for bounded integer range [-10^4, 10^4]
          offset = 10000  # Shift to handle negative indices
          count = [0] * 20001

          # Count occurrences of each value
          for num in nums:
              count[num + offset] += 1

          total = 0
          need_pair = True  # Alternates: True = add to sum, False = skip

          # Iterate through possible values in sorted order
          for val in range(-10000, 10001):
              while count[val + offset] > 0:
                  if need_pair:
                      total += val  # This element is the min of its pair
                  need_pair = not need_pair
                  count[val + offset] -= 1

          return total
    explanation: |
      **Time Complexity:** O(n + k) where k = 20001 (the value range). Effectively O(n) for this problem.

      **Space Complexity:** O(k) = O(20001) = O(1) since k is a constant.

      For the specific constraints of this problem (`-10^4 <= nums[i] <= 10^4`), counting sort achieves O(n) time. We iterate through the count array in order, alternating between adding to the sum (for even-positioned elements) and skipping (for odd-positioned elements).

      While asymptotically faster, this approach is more complex and the constant factors make it slower than comparison sort for typical input sizes.
