A clear explanation of finding the kth largest element using sorting, a min-heap, and Quickselect.
Problem Restatement
We are given an integer array nums and an integer k.
We need to return the kth largest element in the array.
Important detail: this means the kth largest element in sorted order, not the kth distinct element. Duplicates count separately. The official constraints are 1 <= k <= nums.length <= 10^5 and -10^4 <= nums[i] <= 10^4.
Input and Output
| Item | Meaning |
|---|---|
| Input | Integer array nums and integer k |
| Output | The kth largest element |
| Duplicate values | Count separately |
| Challenge | Solve without fully sorting |
Example function shape:
def findKthLargest(nums: list[int], k: int) -> int:
...Examples
Example 1:
nums = [3, 2, 1, 5, 6, 4]
k = 2Sorted descending:
[6, 5, 4, 3, 2, 1]The second largest element is:
5Example 2:
nums = [3, 2, 3, 1, 2, 4, 5, 5, 6]
k = 4Sorted descending:
[6, 5, 5, 4, 3, 3, 2, 2, 1]The fourth largest element is:
4Notice that both 5s count separately.
First Thought: Sort the Array
The simplest solution is to sort the array in descending order and return index k - 1.
class Solution:
def findKthLargest(self, nums: list[int], k: int) -> int:
nums.sort(reverse=True)
return nums[k - 1]This is correct and short.
Problem With Sorting
Sorting gives the complete order of all elements.
But we only need one element.
For n elements, sorting takes:
O(n log n)That is acceptable in many cases, but the problem asks whether we can solve it without fully sorting.
Two common alternatives are:
| Method | Time | Space | Notes |
|---|---|---|---|
Min-heap of size k | O(n log k) | O(k) | Stable and easy |
| Quickselect | Average O(n) | O(1) extra | Fast, but worst-case O(n^2) |
Heap Idea
Keep only the largest k elements seen so far.
Use a min-heap.
The heap stores the current top k largest values.
The smallest value in this heap is the kth largest among the values seen so far.
For each number:
- Push it into the heap.
- If the heap size becomes larger than
k, remove the smallest value. - At the end, the heap root is the answer.
Heap Walkthrough
Use:
nums = [3, 2, 1, 5, 6, 4]
k = 2Process values:
| Number | Heap after keeping top 2 |
|---|---|
| 3 | [3] |
| 2 | [2, 3] |
| 1 | [2, 3] |
| 5 | [3, 5] |
| 6 | [5, 6] |
| 4 | [5, 6] |
At the end, the heap contains the two largest numbers:
[5, 6]The smallest among them is:
5So the second largest element is 5.
Heap Correctness
The heap is maintained so that it contains the largest k values among all elements processed so far.
When a new value is inserted and the heap grows beyond size k, removing the smallest value preserves the largest k values.
After all numbers are processed, the heap contains the largest k values in the entire array. The smallest value among these k values is exactly the kth largest element.
Heap Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n log k) | Each push or pop costs O(log k) |
| Space | O(k) | Heap stores at most k numbers |
Heap Implementation
import heapq
class Solution:
def findKthLargest(self, nums: list[int], k: int) -> int:
heap = []
for num in nums:
heapq.heappush(heap, num)
if len(heap) > k:
heapq.heappop(heap)
return heap[0]Quickselect Idea
Quickselect is based on partitioning.
Instead of sorting the full array, we only search the side that contains the answer.
If the array were sorted ascending, the kth largest element would be at index:
target = len(nums) - kExample:
nums = [3, 2, 1, 5, 6, 4]
k = 2Sorted ascending:
[1, 2, 3, 4, 5, 6]The second largest is at index:
6 - 2 = 4The value at index 4 is 5.
Partitioning
Pick a pivot.
Rearrange the array so that:
| Side | Condition |
|---|---|
| Left side | Values less than pivot |
| Pivot position | Pivot in its final sorted position |
| Right side | Values greater than or equal to pivot |
After partitioning, if the pivot lands at target, we found the answer.
If the pivot lands before target, search the right side.
If the pivot lands after target, search the left side.
Quickselect Algorithm
- Convert
kth largest to ascending index:target = len(nums) - k. - Set search range
[left, right]. - Partition the range.
- Compare pivot index with
target. - Narrow the search range.
- Return
nums[target].
Quickselect Implementation
import random
class Solution:
def findKthLargest(self, nums: list[int], k: int) -> int:
target = len(nums) - k
left = 0
right = len(nums) - 1
while left <= right:
pivot_index = random.randint(left, right)
pivot_index = self.partition(nums, left, right, pivot_index)
if pivot_index == target:
return nums[pivot_index]
if pivot_index < target:
left = pivot_index + 1
else:
right = pivot_index - 1
return -1
def partition(self, nums: list[int], left: int, right: int, pivot_index: int) -> int:
pivot_value = nums[pivot_index]
nums[pivot_index], nums[right] = nums[right], nums[pivot_index]
store_index = left
for i in range(left, right):
if nums[i] < pivot_value:
nums[store_index], nums[i] = nums[i], nums[store_index]
store_index += 1
nums[store_index], nums[right] = nums[right], nums[store_index]
return store_indexCode Explanation
Convert kth largest into sorted ascending index:
target = len(nums) - kChoose a random pivot to reduce the chance of bad partitions:
pivot_index = random.randint(left, right)Partition the current range:
pivot_index = self.partition(nums, left, right, pivot_index)If the pivot lands exactly where we need:
return nums[pivot_index]Otherwise, search only one side:
if pivot_index < target:
left = pivot_index + 1
else:
right = pivot_index - 1Inside partition, move the pivot to the end:
nums[pivot_index], nums[right] = nums[right], nums[pivot_index]Place smaller elements on the left:
if nums[i] < pivot_value:
nums[store_index], nums[i] = nums[i], nums[store_index]
store_index += 1Move the pivot into its final position:
nums[store_index], nums[right] = nums[right], nums[store_index]Return that final pivot index.
Quickselect Correctness
After each partition, the pivot is placed at the same index it would occupy in a fully sorted ascending array.
All elements to its left are smaller than the pivot.
All elements to its right are greater than or equal to the pivot.
If the pivot index equals target, the pivot is the kth largest element.
If the pivot index is smaller than target, the target element must be on the right.
If the pivot index is larger than target, the target element must be on the left.
The algorithm discards the side that cannot contain the answer, while preserving the side that must contain it. Therefore it eventually returns the correct element.
Quickselect Complexity
| Metric | Value | Why |
|---|---|---|
| Average time | O(n) | Each partition usually removes a large part of the search range |
| Worst-case time | O(n^2) | Bad pivots can reduce the range by only one element |
| Extra space | O(1) | Partitioning is done in place |
Random pivot selection makes the bad case unlikely in practice.
Testing
def run_tests():
s = Solution()
assert s.findKthLargest([3,2,1,5,6,4], 2) == 5
assert s.findKthLargest([3,2,3,1,2,4,5,5,6], 4) == 4
assert s.findKthLargest([1], 1) == 1
assert s.findKthLargest([2,1], 1) == 2
assert s.findKthLargest([2,1], 2) == 1
assert s.findKthLargest([-1,-1], 2) == -1
print("all tests passed")
run_tests()| Test | Why |
|---|---|
[3,2,1,5,6,4], 2 | Standard example |
| Duplicates example | Confirms duplicates count separately |
[1], 1 | Single element |
[2,1], 1 | Largest element |
[2,1], 2 | Smallest element as kth largest |
[-1,-1], 2 | Duplicate negative values |