Quickselect and Nth Element
To find the kth largest element you do not need the array sorted — you
need one element in the right place. Sorting delivers n answers when
you asked for one, and charges for the privilege.
Quickselect is quicksort that recurses into only the side containing the answer. Each partition discards a fraction of the array instead of processing both halves, and the geometric series sums to — so the average cost is .
The judgement this page builds is knowing when quickselect is genuinely the right answer, because often it is not.
What you’ll learn
Section titled “What you’ll learn”- Partitioning, and why quickselect recurses once where quicksort recurses twice.
- Why average and worst case, and the one line that makes the worst case vanish in practice.
- Three-way partitioning, and why it matters when values repeat.
- The honest comparison against heaps,
sorted, and bucket sort — and when each wins. - Three real LeetCode problems solved in the browser: 215, 973, 347.
The cue
Section titled “The cue”Visual intuition
Section titled “Visual intuition”Quickselect is quicksort that throws away one side. The partition step is identical — watch it here, then note that quickselect recurses into only one of the two halves:
Quicksort recurses into both halves, giving n log n. Quickselect compares the pivot's final index with k and recurses into one side only — so the work is n + n/2 + n/4 + ... = O(n) expected. Same partition, half the recursion tree, a different complexity class.
How it works
Section titled “How it works”Partition around a pivot so that everything smaller sits left and everything larger sits right. The pivot is now at its final sorted position. Compare that position with the index you want:
- equal — done, no recursion at all;
- target is smaller — recurse left only;
- target is larger — recurse right only.
import random
def quickselect(nums, k):
"""Return the kth SMALLEST element (k is 0-indexed). Mutates nums."""
lo, hi = 0, len(nums) - 1
while True:
if lo == hi:
return nums[lo]
# random pivot -- this is what avoids the O(n^2) worst case in practice
pivot_index = random.randint(lo, hi)
nums[pivot_index], nums[hi] = nums[hi], nums[pivot_index]
pivot = nums[hi]
store = lo # Lomuto partition
for i in range(lo, hi):
if nums[i] < pivot:
nums[store], nums[i] = nums[i], nums[store]
store += 1
nums[store], nums[hi] = nums[hi], nums[store]
if k == store:
return nums[store]
elif k < store:
hi = store - 1 # recurse LEFT only
else:
lo = store + 1 # recurse RIGHT only
print(quickselect([3, 2, 1, 5, 6, 4], 4)) # 5 -- 5th smallest = 2nd largestThe loop is the tail-recursion written out, which keeps space at instead of of stack frames.
Dry run
Section titled “Dry run”quickselect([3, 2, 1, 5, 6, 4], k=4)
Section titled “quickselect([3, 2, 1, 5, 6, 4], k=4)”The pivot is randomised in the real template, so this trace pins it to nums[hi] to be
reproducible. The sorted array is [1, 2, 3, 4, 5, 6], so the answer at k = 4 is 5.
| Window | Pivot | store after partition | Array | Decision |
|---|---|---|---|---|
[0, 5] | 4 | 3 | [3, 2, 1, 4, 6, 5] | 4 > 3 -> lo = 4 |
[4, 5] | 5 | 4 | [3, 2, 1, 4, 5, 6] | 4 == 4 -> return 5 |
Two partitions. Three things this shows that a sort would hide:
The array is never sorted, and does not need to be. The final state is [3, 2, 1, 4, 5, 6] —
positions 3, 4, 5 happen to be right, and [3, 2, 1] is still a mess. That is the entire economy of
quickselect: it only orders what it must pass through. If the caller wants sorted output, this is
the wrong tool.
store is a final position, not a guess. After the first partition the pivot 4 sits at index
3, and it will never move again — everything left of it is smaller, everything right is larger.
That certainty is what licenses discarding a whole side instead of recursing into both, which is the
one difference from quicksort.
k == store returns with no recursion at all. Step 2 finishes on the comparison, not by
narrowing to a single element. The lo == hi guard at the top of the loop is the fallback for when
the window shrinks to one, not the normal exit.
Why randomising the pivot is not optional, measured
Section titled “Why randomising the pivot is not optional, measured”nums = [1, 2, …, 20] — already sorted — searching for k = 0, the minimum:
| Pivot choice | Comparisons |
|---|---|
Fixed nums[hi] | 190 |
| Random, ten runs | 22, 26, 53, 24, 33, 38, 35, 38, 19, 19 |
190 is exactly — the worst case, achieved precisely. With a fixed last-element pivot on
sorted input, every partition peels off exactly one element, so the work is
. The random runs cluster around 20-40, i.e. a small multiple of n.
Sorted input is not exotic — it is one of the most common shapes in a test suite, and LC 215 has
adversarial cases built for exactly this. One random.randint(lo, hi) moves the randomness out of
the data and into your algorithm, which is why the expected bound becomes for every input
rather than for average inputs.
Three-way partitioning: [7, 7, 7, 7, 7], k = 2
Section titled “Three-way partitioning: [7, 7, 7, 7, 7], k = 2”| Depth | Window | Pivot | < 7 | == 7 | > 7 | Next |
|---|---|---|---|---|---|---|
| 0 | [0, 4] | 7 | empty | [0, 4] | empty | 2 is inside the equal block -> return 7 |
One partition, done. Every element landed in the equal block, and k = 2 is inside it, so the
answer is the pivot. Compare against the two-way version on the same input:
| Input | Two-way partitions needed |
|---|---|
[7] * 5, k = 2 | 3 |
[7] * 50, k = 25 | 26 |
26 partitions for 50 elements — about n/2, i.e. partitions each doing work. Two-way
partitioning shuffles equal elements back and forth without shrinking the problem, because < pivot is false for all of them and they all pile up on one side of store. The equal block is what
converts that into a single step.
Three-way on a mixed array with duplicates
Section titled “Three-way on a mixed array with duplicates”[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5], k = 5. Pivot pinned to nums[lo] for reproducibility.
Sorted, the array is [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9], so the answer is 4.
| Depth | Window | Pivot | Resulting split | Array | Next |
|---|---|---|---|---|---|
| 0 | [0, 10] | 3 | <: [0,3) · ==: [3,4] · >: (4,10] | [1,1,2,3,3,9,6,5,5,5,4] | 5 > 4 -> right |
| 1 | [5, 10] | 9 | <: [5,10) · ==: [10,10] | [1,1,2,3,3,6,5,5,5,4,9] | 5 < 10 -> left |
| 2 | [5, 9] | 6 | <: [5,9) · ==: [9,9] | [1,1,2,3,3,5,5,5,4,6,9] | 5 < 9 -> left |
| 3 | [5, 8] | 5 | <: [5,6) · ==: [6,8] | [1,1,2,3,3,4,5,5,5,6,9] | 5 < 6 -> left |
| 4 | [5, 5] | 4 | ==: [5,5] | unchanged | 5 in block -> return 4 |
Depth 3 is the row that earns the three-way partition: the three 5s are placed in one pass as the
block [6, 8] and never examined again. Note also that a bad pivot — 9 at depth 1, the maximum
— costs a level but not correctness; the window still shrank by one element, which is the
worst-case behaviour the randomisation exists to make rare.
The gt-side asymmetry. When swapping from the gt end, i must not advance: the value
swapped in came from an unexamined position and still needs classifying. Advancing i there skips
it, which quietly misplaces elements — the array still looks partitioned and the answer is wrong.
The lt side is different because the value swapped in from lt has already been seen.
Complexity, honestly
Section titled “Complexity, honestly”| Approach | Time | Space | Notes |
|---|---|---|---|
sorted(nums)[k] | One line; often fast enough and the right first answer | ||
| Quickselect | average, worst | Mutates the input; unordered output | |
Size-k heap | Best when k is small or data streams | ||
heapq.nsmallest(k, ...) | Stdlib, sorted output, no mutation | ||
| Bucket / counting sort | Only when the value range is bounded |
Three-way partitioning
Section titled “Three-way partitioning”With many duplicate values, two-way partitioning wastes work: equal elements
get shuffled repeatedly without shrinking the problem. A three-way (Dutch
national flag) partition splits into < pivot, == pivot, > pivot, and
the entire equal block is finished in one pass.
import random
def kth_smallest(nums, k):
def select(lo, hi):
pivot = nums[random.randint(lo, hi)]
lt, i, gt = lo, lo, hi
while i <= gt: # Dutch national flag
if nums[i] < pivot:
nums[lt], nums[i] = nums[i], nums[lt]
lt += 1
i += 1
elif nums[i] > pivot:
nums[i], nums[gt] = nums[gt], nums[i]
gt -= 1 # do NOT advance i here
else:
i += 1
# now: [lo, lt) < pivot, [lt, gt] == pivot, (gt, hi] > pivot
if k < lt:
return select(lo, lt - 1)
if k > gt:
return select(gt + 1, hi)
return nums[k] # inside the equal block
return select(0, len(nums) - 1)
print(kth_smallest([7, 7, 7, 7, 7], 2)) # 7 -- one partition finishes itOn [7,7,7,7,7] the first partition puts everything in the equal block and
returns immediately. Two-way partitioning would recurse times on the
same input.
Note the asymmetry: when swapping from the gt end you must not advance
i, because the value swapped in is unexamined. Advancing it there is the
classic Dutch-flag bug.
The variant map
Section titled “The variant map”| Variant | The change | Canonical problem |
|---|---|---|
kth smallest | The template as written, 0-indexed | — |
kth largest | Select index n - k, or negate every value | 215 |
Top k elements (not just the kth) | Partition once at n - k; the whole right side is the answer, unordered | 215 · 973 |
k closest to a point | Same, but partition on the distance as the key | 973 K Closest Points to Origin |
Top k frequent | Count first, then select over (value, count) pairs — or bucket sort, which is worst case | 347 |
| Many duplicate values | Three-way (Dutch flag) partition; the equal block finishes in one pass | 215 with heavy duplicates |
| Median | Select at n // 2; for an even count select twice | 4 · 462 |
| Wiggle / partition into halves | Select the median, then three-way partition around it | 324 Wiggle Sort II |
Streaming, k fixed | Quickselect needs the whole array — use a size-k heap instead | 703 Kth Largest in a Stream |
| Deterministic worst case | Median-of-medians pivot; name it, do not implement it | — |
| Bounded value range | Counting or bucket sort — a true with no randomisation | 347 · 692 |
Practice — real LeetCode problems
Section titled “Practice — real LeetCode problems”LC 215 — Kth Largest Element in an Array · Medium
Section titled “LC 215 — Kth Largest Element in an Array · Medium”Problem. Given an integer array nums and an integer k, return the
kth largest element. Note this is the kth largest in sorted order, not
the kth distinct element.
Constraints. 1 <= k <= len(nums) <= 10^5,
-10^4 <= nums[i] <= 10^4. Can you solve it without sorting?
Examples. nums = [3,2,1,5,6,4], k = 2 gives 5 ·
nums = [3,2,3,1,2,4,5,5,6], k = 4 gives 4
Editorial — approach, complexity, follow-ups
Convert the “kth largest” into an ascending index once, at the top:
target = len(nums) - k. Doing that conversion up front avoids a whole class
of off-by-one errors that come from carrying “largest” semantics through the
recursion.
Then partition and recurse into whichever side contains target.
Time expected, worst case (astronomically unlikely with a random pivot). Space for the partitioning, plus expected recursion depth — convertible to by looping instead.
([7,7,7], 2) is the duplicate case: three-way partitioning resolves it in
one step, while a two-way version recurses repeatedly on identical values.
Two answers worth giving alongside:
heapq.nlargest(k, nums)[-1]— , one line. Genuinely better whenkis small.sorted(nums)[-k]— , the baseline. State it, then improve on it.
Follow-ups you should expect: “What if you cannot mutate the input?” —
copy first ( space) or use a heap. “kth distinct largest?” —
deduplicate into a set first, which changes the answer. “Streaming
(LC 703)?” — quickselect cannot; a size-k min-heap can. “Guarantee
worst case?” — median-of-medians; name it and note the constant
factor makes it impractical.
LC 973 — K Closest Points to Origin · Medium
Section titled “LC 973 — K Closest Points to Origin · Medium”Problem. Given an array of points on the plane and an integer k,
return the k closest points to the origin, by Euclidean distance. The
answer may be returned in any order.
Constraints. 1 <= k <= len(points) <= 10^4,
-10^4 <= xi, yi <= 10^4.
Examples. points = [[1,3],[-2,2]], k = 1 gives [[-2,2]] ·
points = [[3,3],[5,-1],[-2,4]], k = 2 gives [[3,3],[-2,4]]
Editorial — approach, complexity, follow-ups
Two observations do most of the work:
- Never compute a square root. exactly when for non-negative values, so comparing gives the same ordering with integer arithmetic — faster, and no floating-point tie-breaking weirdness.
- “Any order” is permission to be fast. If the result had to be sorted by distance you would pay regardless.
heapq.nsmallest: time, space. Idiomatic and hard
to get wrong.
Quickselect on squared distance: average, extra space, and
returns points[:k] after partitioning. Better asymptotically, more code,
mutates the input.
For these are close in practice. State both, note that
nsmallest wins on clarity while quickselect wins on asymptotics, and pick
one. A max-heap of size k (pushing and popping the farthest) is a third
answer worth mentioning — it is the one that extends to a
stream.
Follow-ups you should expect: “The points arrive as a stream?” — a
size-k max-heap works; quickselect cannot. “Sorted by distance?” —
nsmallest already returns sorted; after quickselect you sort just the k.
“Closest to an arbitrary point, not the origin?” — subtract that point
first. “Ties in distance?” — the problem accepts any, so no special
handling.
LC 347 — Top K Frequent Elements · Medium
Section titled “LC 347 — Top K Frequent Elements · Medium”Problem. Given an integer array nums and an integer k, return the
k most frequent elements, in any order.
Constraints. 1 <= len(nums) <= 10^5, the answer is guaranteed unique.
Your algorithm must be better than .
Examples. nums = [1,1,1,2,2,3], k = 2 gives [1,2] ·
nums = [1], k = 1 gives [1]
Editorial — approach, complexity, follow-ups
Count frequencies in , then select the top k counts. The interesting
part is how you select, and this is the problem where quickselect is
not the best answer.
| Method | Time | Note |
|---|---|---|
| Sort the counts | Explicitly ruled out by the constraints | |
most_common(k) | Uses a heap internally; clean and fast | |
Size-k heap by hand | Same, spelled out | |
| Quickselect on counts | average | Randomised worst case |
| Bucket by frequency | worst case | Frequencies are bounded by n |
Bucket sort wins here and is worth knowing, because the value range is
bounded: no frequency can exceed len(nums), so you can index directly.
buckets = [[] for _ in range(len(nums) + 1)]
for value, count in Counter(nums).items():
buckets[count].append(value)
out = []
for count in range(len(buckets) - 1, 0, -1): # walk from high to low
for value in buckets[count]:
out.append(value)
if len(out) == k:
return outThat is a genuine with no randomisation — strictly better than quickselect’s expected . Recognising a bounded range and reaching for counting/bucket techniques is the transferable lesson.
most_common(k) is the answer to write first: it satisfies the
better-than- requirement, is one line, and is obviously
correct. Then offer the bucket version when asked to beat .
Follow-ups you should expect: “Truly ?” — bucket by frequency. “Ties?” — the problem guarantees uniqueness; otherwise clarify the tie-break (LC 692 makes it lexicographic, which changes the comparator). “Top k frequent words (LC 692)?” — same counting, but the tie-break forces a custom sort key. “Streaming?” — counts change over time; you need a heap plus lazy deletion, or a count-min sketch for approximate answers at scale.
LeetCode problem set
Section titled “LeetCode problem set”Generated from the problem database, so each entry carries its sheet membership and reported companies. Tick them off as you go — progress is saved in this browser, and the Export button writes it to a file you can keep.
Work down the ladder. Tick each problem off as you go — progress is saved in this browser, and the Export button in the filter bar writes it to a file you can keep.
- 347Top K Frequent ElementsmediumBucket by frequency for a true $O(n)$; the range is bounded
- 215Kth Largest Element in an ArraymediumThe base quickselect; randomise the pivot
- 324Wiggle Sort IImedium
- 692Top K Frequent WordsmediumLexicographic tie-break makes the comparator the hard part
- 973K Closest Points to OriginmediumSelect on squared distance -- never take a square root
Interview follow-ups
Section titled “Interview follow-ups”| They ask | What they’re checking | The answer |
|---|---|---|
| “Better than sorting?” | Whether you know selection exists | Quickselect: average by recursing into one side only |
| “Why ?” | Whether you can justify it | Each partition discards a constant fraction, so |
| “Worst case?” | Honesty | if pivots split badly; a random pivot makes that negligible, and median-of-medians makes it guaranteed at a bad constant |
| “Quickselect or a heap?” | Judgement | Heap when k is small, data streams, or you must not mutate; quickselect when the whole array is in memory and k is comparable to n |
| “Many duplicates?” | Depth | Three-way partitioning finishes the equal block in one pass |
| “Can you avoid mutating the input?” | Care | Quickselect reorders in place; copy first or use a heap |
| “Truly worst case here?” | Recognising bounded ranges | If values (or counts) are bounded, bucket/counting sort gives with no randomisation |
Edge-case checklist
Section titled “Edge-case checklist”k == 1andk == len(nums)— the extremes of the range.- Single element —
([1], 1)must work with no partitioning. - All identical values —
[7,7,7]; the case three-way partitioning exists for. - Already sorted, or reverse sorted — the fixed-pivot killer; proves you randomised.
- Negative values — fine for comparisons; only a problem if you assumed non-negative for bucketing.
- kth largest vs. kth distinct largest — different answers; clarify.
- Duplicates in the answer region — LC 215 counts repeats, so
[3,2,3,1,2,4,5,5,6]withk=4gives4. - Mutating the caller’s list — a real design concern; note it or copy.
Self-check
Section titled “Self-check”-
What does quickselect do differently from quicksort?
Partitioning puts the pivot at its final sorted position, so comparing that position with k rules out an entire side with certainty. Quicksort must sort both halves; quickselect discards one. That single change is what turns O(n log n) into O(n) expected -- the recurrence becomes n + n/2 + n/4 + ... = 2n rather than n log n.
pch.quizShowAnswer
B — After partitioning it recurses into only one side, because the pivot's position is final and tells you which side holds index k — Partitioning puts the pivot at its final sorted position, so comparing that position with k rules out an entire side with certainty. Quicksort must sort both halves; quickselect discards one. That single change is what turns O(n log n) into O(n) expected -- the recurrence becomes n + n/2 + n/4 + ... = 2n rather than n log n.
-
You use a fixed pivot of nums[hi] and the input is already sorted. What happens?
Measured on [1..20] searching for the minimum: 190 comparisons with a fixed pivot, which is exactly n(n-1)/2. Ten random-pivot runs on the same input ranged from 19 to 53. Sorted input is one of the most common shapes in a test suite, and LC 215 ships adversarial cases built for this. The fix is one line.
pch.quizShowAnswer
B — Every partition peels off exactly one element, giving n(n-1)/2 comparisons -- 190 for n = 20, the exact worst case — Measured on [1..20] searching for the minimum: 190 comparisons with a fixed pivot, which is exactly n(n-1)/2. Ten random-pivot runs on the same input ranged from 19 to 53. Sorted input is one of the most common shapes in a test suite, and LC 215 ships adversarial cases built for this. The fix is one line.
-
Why does randomising the pivot give an O(n) expected bound for *every* input, not just average ones?
This is the distinction between average-case and randomised analysis. An adversary who knows your code but not your coin flips cannot construct a bad input, because badness depends on the flips. The worst case is still O(n^2) -- it just now requires improbable luck rather than a particular input. Median-of-medians gives a deterministic O(n) worst case, but with a constant factor bad enough that naming it is the right depth.
pch.quizShowAnswer
B — The randomness lives in the algorithm rather than the data, so no fixed input can be adversarial against it — This is the distinction between average-case and randomised analysis. An adversary who knows your code but not your coin flips cannot construct a bad input, because badness depends on the flips. The worst case is still O(n^2) -- it just now requires improbable luck rather than a particular input. Median-of-medians gives a deterministic O(n) worst case, but with a constant factor bad enough that naming it is the right depth.
-
For [7, 7, 7, 7, 7] with k = 2, how do two-way and three-way partitioning compare?
Three-way puts every element in the equal block on the first pass, and k lands inside it, so the pivot is the answer. Two-way asks `< pivot`, which is false for all of them, so they all pile on one side of store and the window shrinks by one per pass -- measured at 3 partitions for n = 5 and 26 for n = 50. Each of those partitions is O(n), so the total is quadratic.
pch.quizShowAnswer
B — Three-way finishes in one partition; two-way needs O(n) of them -- 26 partitions for n = 50 — Three-way puts every element in the equal block on the first pass, and k lands inside it, so the pivot is the answer. Two-way asks `< pivot`, which is false for all of them, so they all pile on one side of store and the window shrinks by one per pass -- measured at 3 partitions for n = 5 and 26 for n = 50. Each of those partitions is O(n), so the total is quadratic.
-
In the Dutch-flag partition, why must `i` not advance after swapping with the `gt` end?
The classic Dutch-flag bug. Values below `i` have been classified; values at and above `gt` have not. Swapping from `gt` brings an unknown value to position `i`, so it must be examined next iteration. The `lt` side is genuinely different -- the value coming from `lt` was already seen. Advance `i` on the `gt` swap and elements are silently misplaced: the array still looks partitioned and the answer is wrong.
pch.quizShowAnswer
B — The value swapped in came from an unexamined position and still needs classifying — The classic Dutch-flag bug. Values below `i` have been classified; values at and above `gt` have not. Swapping from `gt` brings an unknown value to position `i`, so it must be examined next iteration. The `lt` side is genuinely different -- the value coming from `lt` was already seen. Advance `i` on the `gt` swap and elements are silently misplaced: the array still looks partitioned and the answer is wrong.
-
LC 347 asks for the k most frequent elements. Is quickselect the best answer?
A count cannot exceed n, so you can index buckets directly by frequency and read them off in one pass: true O(n) with a deterministic worst case. Quickselect on the (value, count) pairs works and is a fine second answer, but reaching for it here reads as pattern matching rather than looking at the constraint. A size-k heap is O(n log k), the right answer when k is very small or the data streams.
pch.quizShowAnswer
B — No -- frequencies are bounded by n, so bucket sort is O(n) worst case, cleaner, and needs no randomisation — A count cannot exceed n, so you can index buckets directly by frequency and read them off in one pass: true O(n) with a deterministic worst case. Quickselect on the (value, count) pairs works and is a fine second answer, but reaching for it here reads as pattern matching rather than looking at the constraint. A size-k heap is O(n log k), the right answer when k is very small or the data streams.
-
The problem asks for all k largest elements, not just the kth. Does quickselect still apply?
Partitioning at n - k places every one of the k largest to the right of that index, in arbitrary order. If the problem needs them sorted, sorting just that slice is O(n + k log k), still better than O(n log n) for the whole array. Noticing that "the top k" and "the top k, sorted" are different requirements is worth a sentence out loud.
pch.quizShowAnswer
B — Yes -- partition once at index n - k and the whole right side is the answer set, unordered, at O(n) expected — Partitioning at n - k places every one of the k largest to the right of that index, in arbitrary order. If the problem needs them sorted, sorting just that slice is O(n + k log k), still better than O(n log n) for the whole array. Noticing that "the top k" and "the top k, sorted" are different requirements is worth a sentence out loud.
-
When should you *not* reach for quickselect?
Quickselect mutates in place, needs the whole array resident, and has a randomised worst case. `heapq.nlargest(k, ...)` is O(n log k), three characters of code, non-mutating, and returns sorted output -- often the better answer when k is much smaller than n. A stream rules quickselect out entirely; use a size-k heap. Duplicates are handled fine with a three-way partition.
pch.quizShowAnswer
B — When the input must not be mutated, when the data streams, or when a bounded value range makes counting sort a deterministic O(n) — Quickselect mutates in place, needs the whole array resident, and has a randomised worst case. `heapq.nlargest(k, ...)` is O(n log k), three characters of code, non-mutating, and returns sorted output -- often the better answer when k is much smaller than n. A stream rules quickselect out entirely; use a size-k heap. Duplicates are handled fine with a three-way partition.
Recall card
Section titled “Recall card”- Partition, then recurse into one side only. The pivot’s post-partition index is its final
sorted position, so comparing it with
kdiscards a whole side with certainty. That is the only difference from quicksort, and it is what gives expected: . - Randomise the pivot — one line, not optional. Fixed
nums[hi]on sorted input costs exactly : 190 comparisons forn = 20, against 19-53 with a random pivot. - Randomised, not average-case. The randomness is in the algorithm, so no input can be adversarial — only bad luck can.
- Write the loop, not the recursion. Tail position, so iterating keeps space at .
k == storereturns immediately. Thelo == higuard is the fallback, not the normal exit.- Heavy duplicates need three-way (Dutch flag). All-equal input: one partition instead of ~
n/2(measured 26 forn = 50). - On the
gtswap, do not advancei— that value is unexamined. The classic Dutch-flag bug, and it misplaces elements silently. - The array comes back unsorted, and mutated. If the caller needs order or an untouched input, this is the wrong tool.
kth largest = select atn - k. For all topk, one partition atn - kand take the right side; sort that slice only if order is required, at .- Frequently not the best answer:
heapq.nlargestis and non-mutating; bucket sort is a deterministic when values are bounded (LC 347); a stream needs a size-kheap. State the sorting baseline, then choose from the constraints.
- Quickselect is quicksort that recurses into one side only, giving average instead of — because .
- Randomise the pivot. A fixed pivot is on sorted input, and sorted input is common in tests.
- Convert “kth largest” to an ascending index once, at the top.
- Three-way partitioning for duplicate-heavy data — the equal block
finishes in a single pass, and do not advance
iwhen swapping from thegtend. - Quickselect is not automatically the right answer. A size-
kheap wins for smallk, streams, and immutable input; bucket sort wins outright when the value range is bounded (LC 347). - Selection needs the whole array in memory. For medians over a stream, use Two Heaps.
Next: Top K Elements — the heap-based view of the same family, and the patterns where a heap is the only option.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading