Kth Largest Element in Array

Find the kth largest element in an unsorted array

Language Selection

Choose your preferred programming language

Showing: Python

Kth Largest Element in Array

Problem Statement

Given an integer array nums and an integer k, return the kth largest element in the array. Note that it is the kth largest element in the sorted order, not the kth distinct element.

You must solve it in O(n) time complexity.

Input/Output Specifications

  • Input:
    • nums: Array of integers
    • k: Integer (1 <= k <= nums.length)
  • Output: Integer representing the kth largest element

Constraints

  • 1 <= k <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4

Examples

Example 1:

Input: nums = [3,2,1,5,6,4], k = 2
Output: 5
Explanation: The sorted array is [1,2,3,4,5,6]. The 2nd largest element is 5.

Example 2:

Input: nums = [3,2,3,1,2,4,5,5,6], k = 4
Output: 4
Explanation: The sorted array is [1,2,2,3,3,4,5,5,6]. The 4th largest element is 4.

Solution Approaches

Approach 1: Min-Heap (Priority Queue) - Good for Small K

Algorithm Explanation:

  1. Use a min-heap of size k to maintain the k largest elements
  2. Iterate through array:
    • If heap size < k, add element
    • If element > heap top, remove top and add element
  3. Return heap top (kth largest element)

Implementation:

Python:

import heapq

def find_kth_largest_heap(nums, k):
    """
    Find kth largest element using min-heap
    Time: O(n log k)
    Space: O(k)
    """
    min_heap = []
    
    for num in nums:
        if len(min_heap) < k:
            heapq.heappush(min_heap, num)
        elif num > min_heap[0]:
            heapq.heapreplace(min_heap, num)
    
    return min_heap[0]  # kth largest element

# Alternative using heapq.nlargest
def find_kth_largest_builtin(nums, k):
    """
    Find kth largest using built-in function
    Time: O(n log k)
    Space: O(k)
    """
    return heapq.nlargest(k, nums)[-1]

Java:

import java.util.*;

class Solution {
    /**
     * Find kth largest element using min-heap
     * Time: O(n log k)
     * Space: O(k)
     */
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> minHeap = new PriorityQueue<>(k);
        
        for (int num : nums) {
            if (minHeap.size() < k) {
                minHeap.offer(num);
            } else if (num > minHeap.peek()) {
                minHeap.poll();
                minHeap.offer(num);
            }
        }
        
        return minHeap.peek();
    }
}

Go:

import (
    "container/heap"
)

type MinHeap []int

func (h MinHeap) Len() int           { return len(h) }
func (h MinHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h MinHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }

func (h *MinHeap) Push(x interface{}) {
    *h = append(*h, x.(int))
}

func (h *MinHeap) Pop() interface{} {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}

// findKthLargest - Find kth largest element using min-heap
// Time: O(n log k), Space: O(k)
func findKthLargest(nums []int, k int) int {
    minHeap := &MinHeap{}
    heap.Init(minHeap)
    
    for _, num := range nums {
        if minHeap.Len() < k {
            heap.Push(minHeap, num)
        } else if num > (*minHeap)[0] {
            heap.Pop(minHeap)
            heap.Push(minHeap, num)
        }
    }
    
    return (*minHeap)[0]
}

JavaScript:

class MinHeap {
    constructor() {
        this.heap = [];
    }
    
    push(val) {
        this.heap.push(val);
        this.bubbleUp();
    }
    
    pop() {
        if (this.heap.length === 0) return null;
        if (this.heap.length === 1) return this.heap.pop();
        
        const min = this.heap[0];
        this.heap[0] = this.heap.pop();
        this.bubbleDown();
        return min;
    }
    
    peek() {
        return this.heap[0];
    }
    
    size() {
        return this.heap.length;
    }
    
    bubbleUp() {
        let idx = this.heap.length - 1;
        while (idx > 0) {
            const parentIdx = Math.floor((idx - 1) / 2);
            if (this.heap[parentIdx] <= this.heap[idx]) break;
            [this.heap[parentIdx], this.heap[idx]] = [this.heap[idx], this.heap[parentIdx]];
            idx = parentIdx;
        }
    }
    
    bubbleDown() {
        let idx = 0;
        while (2 * idx + 1 < this.heap.length) {
            let minChildIdx = 2 * idx + 1;
            if (2 * idx + 2 < this.heap.length && 
                this.heap[2 * idx + 2] < this.heap[minChildIdx]) {
                minChildIdx = 2 * idx + 2;
            }
            
            if (this.heap[idx] <= this.heap[minChildIdx]) break;
            [this.heap[idx], this.heap[minChildIdx]] = [this.heap[minChildIdx], this.heap[idx]];
            idx = minChildIdx;
        }
    }
}

/**
 * Find kth largest element using min-heap
 * Time: O(n log k)
 * Space: O(k)
 */
function findKthLargest(nums, k) {
    const minHeap = new MinHeap();
    
    for (const num of nums) {
        if (minHeap.size() < k) {
            minHeap.push(num);
        } else if (num > minHeap.peek()) {
            minHeap.pop();
            minHeap.push(num);
        }
    }
    
    return minHeap.peek();
}

C#:

using System;
using System.Collections.Generic;

public class Solution {
    /// <summary>
    /// Find kth largest element using min-heap
    /// Time: O(n log k)
    /// Space: O(k)
    /// </summary>
    public int FindKthLargest(int[] nums, int k) {
        var minHeap = new PriorityQueue<int, int>();
        
        foreach (int num in nums) {
            if (minHeap.Count < k) {
                minHeap.Enqueue(num, num);
            } else if (num > minHeap.Peek()) {
                minHeap.Dequeue();
                minHeap.Enqueue(num, num);
            }
        }
        
        return minHeap.Peek();
    }
}

Approach 2: Quickselect (Optimal - O(n) Average)

Algorithm Explanation:

  1. Use quickselect algorithm to find kth largest element
  2. Partition array around pivot, placing larger elements on left
  3. Recursively search in appropriate partition
  4. Return element when partition index equals k-1

Implementation:

Python:

import random

def find_kth_largest_quickselect(nums, k):
    """
    Find kth largest element using quickselect
    Time: O(n) average, O(n²) worst case
    Space: O(1)
    """
    def quickselect(left, right, k_smallest):
        """Find k-th smallest element in nums[left:right+1]"""
        if left == right:
            return nums[left]
        
        # Random pivot for better average performance
        pivot_index = random.randint(left, right)
        pivot_index = partition(left, right, pivot_index)
        
        if k_smallest == pivot_index:
            return nums[k_smallest]
        elif k_smallest < pivot_index:
            return quickselect(left, pivot_index - 1, k_smallest)
        else:
            return quickselect(pivot_index + 1, right, k_smallest)
    
    def partition(left, right, pivot_index):
        """Partition array for quickselect (descending order for kth largest)"""
        pivot_value = nums[pivot_index]
        
        # Move pivot to end
        nums[pivot_index], nums[right] = nums[right], nums[pivot_index]
        
        # Partition: larger elements go to left
        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
        
        # Move pivot to final position
        nums[right], nums[store_index] = nums[store_index], nums[right]
        return store_index
    
    # Convert kth largest to (k-1)th in 0-indexed array sorted in descending order
    return quickselect(0, len(nums) - 1, k - 1)

# Iterative version
def find_kth_largest_quickselect_iterative(nums, k):
    """
    Iterative quickselect implementation
    Time: O(n) average, O(n²) worst case
    Space: O(1)
    """
    def partition(left, right, pivot_index):
        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:  # For kth largest
                nums[store_index], nums[i] = nums[i], nums[store_index]
                store_index += 1
        
        nums[right], nums[store_index] = nums[store_index], nums[right]
        return store_index
    
    left, right = 0, len(nums) - 1
    k_smallest = k - 1
    
    while left <= right:
        pivot_index = random.randint(left, right)
        pivot_index = partition(left, right, pivot_index)
        
        if k_smallest == pivot_index:
            return nums[k_smallest]
        elif k_smallest < pivot_index:
            right = pivot_index - 1
        else:
            left = pivot_index + 1
    
    return -1  # Should never reach here

Approach 3: Sorting (Simple but Not Optimal)

Algorithm Explanation:

  1. Sort the array in descending order
  2. Return element at index k-1

Implementation:

Python:

def find_kth_largest_sorting(nums, k):
    """
    Find kth largest using sorting
    Time: O(n log n)
    Space: O(1) if in-place sort allowed
    """
    nums.sort(reverse=True)
    return nums[k - 1]

Key Insights

  • Min-Heap Strategy: Maintain k largest elements, root is kth largest
  • Quickselect Efficiency: Average O(n) but worst-case O(n²)
  • Random Pivot: Improves quickselect average performance
  • Partitioning: For kth largest, partition with larger elements on left

Edge Cases

  1. k = 1: Return maximum element
  2. k = n: Return minimum element
  3. Single Element: Array with one element
  4. Duplicate Values: Multiple occurrences of kth largest
  5. All Same Values: All elements identical

Test Cases

def test_find_kth_largest():
    # Basic test cases
    assert find_kth_largest_heap([3,2,1,5,6,4], 2) == 5
    assert find_kth_largest_heap([3,2,3,1,2,4,5,5,6], 4) == 4
    
    # Edge cases
    assert find_kth_largest_heap([1], 1) == 1
    assert find_kth_largest_heap([1,2], 1) == 2
    assert find_kth_largest_heap([1,2], 2) == 1
    
    # All same values
    assert find_kth_largest_heap([5,5,5,5], 2) == 5
    
    # Negative numbers
    assert find_kth_largest_heap([-1,-3,2,0,1], 2) == 1
    
    # Test quickselect as well
    assert find_kth_largest_quickselect([3,2,1,5,6,4], 2) == 5
    assert find_kth_largest_quickselect([3,2,3,1,2,4,5,5,6], 4) == 4
    
    print("All tests passed!")

test_find_kth_largest()

Interview Tips

  1. Ask About Constraints: Is k always valid? Can we modify input array?
  2. Discuss Trade-offs: Heap vs quickselect vs sorting
  3. Mention Optimization: Random pivot for quickselect
  4. Handle Edge Cases: k=1, k=n, duplicates, single element
  5. Follow-up Questions: Be ready to implement both heap and quickselect

Follow-up Questions

  1. Kth Smallest Element: Modify for finding kth smallest
  2. Streaming Data: Handle continuous stream of numbers
  3. Memory Constraint: What if only O(1) extra space allowed?
  4. Multiple Queries: Multiple k values for same array
  5. Distinct Elements: Find kth largest distinct element

Common Mistakes

  1. Wrong Heap Type: Using max-heap instead of min-heap
  2. Index Confusion: Off-by-one errors with k and array indices
  3. Partition Logic: Wrong comparison in quickselect partition
  4. Worst Case: Not considering O(n²) worst case of quickselect
  5. Duplicate Handling: Not handling duplicate values correctly

Concept Explanations

Why Min-Heap for Kth Largest: A min-heap of size k maintains the k largest elements seen so far. The root (minimum of these k elements) is exactly the kth largest element overall.

Quickselect Algorithm: Based on quicksort partitioning but only recurses into one side. For kth largest, we partition so that larger elements are on the left, then search accordingly.

Time Complexity Analysis:

  • Heap: O(n log k) - each element processed once with O(log k) heap operations
  • Quickselect: O(n) average - each recursion eliminates roughly half the elements
  • Sorting: O(n log n) - standard comparison-based sorting bound

When to Apply: Use heap approach when k is small relative to n. Use quickselect when you need guaranteed O(n) average performance and can modify the input array. Use sorting when simplicity is preferred and O(n log n) is acceptable.