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 1st and 2nd smallest, the 3rd and 4th 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
**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
**Step 3: Return the sum**
- The sum of elements at even indices is the maximum possible sum of minimums
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.