191 lines
8.5 KiB
YAML
191 lines
8.5 KiB
YAML
title: Array Partition
|
|
slug: array-partition
|
|
difficulty: easy
|
|
leetcode_id: 561
|
|
leetcode_url: https://leetcode.com/problems/array-partition/
|
|
categories:
|
|
- arrays
|
|
- sorting
|
|
patterns:
|
|
- slug: greedy
|
|
is_optimal: true
|
|
|
|
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
|
|
|
|
|
|
|
|
**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.
|