Skip to content

215. Kth Largest Element in an Array (Medium)

Problem

Given an integer array nums and an integer k, return the k-th largest element in the array. Note: it’s the k-th largest in sorted order, not distinct.

Can you do this without sorting?

Example

  • nums = [3,2,1,5,6,4], k = 25
  • nums = [3,2,3,1,2,4,5,5,6], k = 44

LeetCode 215 · Link · Medium

Approach 1: Brute force, sort, index

def find_kth_largest(nums, k):
nums.sort() # L1: O(n log n)
return nums[-k] # L2: O(1) index

Where the time goes, line by line

Variables: n = number of elements in nums, k = the rank parameter.

LinePer-call costTimes executedContribution
L1 (sort)O(n log n)1O(n log n) ← dominates
L2 (index)O(1)1O(1)

Complexity

  • Time: O(n log n), driven by L1.
  • Space: O(1) or O(log n) depending on sort.

Fastest to write; doesn’t meet the “without sorting” challenge.

Approach 2: Size-K min-heap

Maintain a min-heap of size k; the top is the kth largest.

import heapq
def find_kth_largest(nums, k):
heap = []
for x in nums: # L1: iterate n elements
heapq.heappush(heap, x) # L2: O(log k) push
if len(heap) > k:
heapq.heappop(heap) # L3: O(log k) pop to keep size k
return heap[0]
# Equivalent one-liner:
# return heapq.nlargest(k, nums)[-1]

Where the time goes, line by line

Variables: n = number of elements in nums, k = the rank parameter.

LinePer-call costTimes executedContribution
L1 (loop)O(1)nO(n)
L2 (heappush)O(log k)nO(n log k) ← dominates
L3 (heappop)O(log k)n - kO(n log k)

The heap never exceeds k entries. Every push and pop costs O(log k). Since we process n elements, total cost is O(n log k).

Complexity

  • Time: O(n log k), driven by L2/L3.
  • Space: O(k).

Approach 3: Quickselect (optimal average)

Partition-based selection; average linear time.

import random
def find_kth_largest(nums, k):
# k-th largest = (n - k)-th smallest (0-indexed)
target = len(nums) - k
def partition(lo, hi):
pivot = nums[random.randint(lo, hi)] # L1: O(1) random pivot
left, right = lo, hi
i = lo
while i <= right: # L2: three-way partition
if nums[i] < pivot:
nums[left], nums[i] = nums[i], nums[left]
left += 1; i += 1
elif nums[i] > pivot:
nums[right], nums[i] = nums[i], nums[right]
right -= 1
else:
i += 1
return left, right # pivot's final range [left, right]
def quickselect(lo, hi):
while True:
if lo == hi:
return nums[lo]
l, r = partition(lo, hi) # L3: O(hi - lo) per call
if l <= target <= r:
return nums[target]
elif target < l:
hi = l - 1
else:
lo = r + 1
return quickselect(0, len(nums) - 1)

Where the time goes, line by line

Variables: n = number of elements in nums, k = the rank parameter.

LinePer-call costTimes executedContribution
L1 (random pivot)O(1)log n avg roundsO(log n)
L2 (three-way partition)O(subarray size)log n avg roundsO(n) avg ← dominates
L3 (partition call)O(n) first roundlog n avgO(n) avg

On average, each round halves the search space: n + n/2 + n/4 + … = 2n = O(n). With random pivot, worst case O(n²) is extremely unlikely.

Complexity

  • Time: O(n) average, O(n²) worst case (mitigated by random pivot at L1).
  • Space: O(1) extra (iterative loop avoids recursion).

The three-way partition handles duplicate values efficiently, important when the array contains many repeats.

Test cases

# Quick smoke tests, paste into a REPL or save as test_215.py and run.
# Uses the size-K min-heap approach (Approach 2).
import heapq
def find_kth_largest(nums, k):
heap = []
for x in nums:
heapq.heappush(heap, x)
if len(heap) > k:
heapq.heappop(heap)
return heap[0]
def _run_tests():
# Examples from problem statement
assert find_kth_largest([3,2,1,5,6,4], 2) == 5
assert find_kth_largest([3,2,3,1,2,4,5,5,6], 4) == 4
# k = 1: largest
assert find_kth_largest([1], 1) == 1
# All same
assert find_kth_largest([2,2,2,2], 2) == 2
# k = n: smallest
assert find_kth_largest([5,3,1,4,2], 5) == 1
print("all tests pass")
if __name__ == "__main__":
_run_tests()

Summary

ApproachTimeSpace
SortO(n log n)O(1)
Size-K min-heapO(n log k)O(k)
QuickselectO(n) avg / O(n²) worstO(1)

The heap is the interview-safe answer. Quickselect is the “show you understand selection algorithms” answer. In practice, std-lib nlargest is often the fastest due to constant factors.