questions A (01-matrix - avoid-flood)

This commit is contained in:
2025-05-24 21:40:39 +01:00
parent 09ec96a282
commit 0b83eff6f8
55 changed files with 10813 additions and 0 deletions

View File

@@ -0,0 +1,169 @@
title: Array Partition
slug: array-partition
difficulty: easy
leetcode_id: 561
leetcode_url: https://leetcode.com/problems/array-partition/
categories:
- arrays
- sorting
patterns:
- greedy
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.