数组中的第 K 个最大元素(LeetCode215)
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
问题描述
需要找到排序后数组中的第 k 个最大的元素,而不是第 k 个不同的元素。要求设计并实现时间复杂度为 O(n) 的算法解决此问题。
方法1:快速选择算法(Quickselect)
快速选择算法是快速排序的一种变体,能够在平均情况下达到 O(n) 的时间复杂度。其核心思想是基于快速排序的分区思想,通过一次分区将数组分为两部分,然后判断第 k 大元素在哪一部分,递归或迭代进行选择。
实现步骤:
- 随机选择一个基准元素,并将数组分为大于和小于基准的两部分。
 - 判断基准元素的位置与 
k的关系。 - 递归或迭代在适当的部分继续查找,直到找到第 
k大的元素。 
代码实现:
const findKthLargest = function(nums, k) {
    const partition = (left, right) => {
        const pivot = nums[right];
        let pIndex = left;
        for (let i = left; i < right; i++) {
            if (nums[i] >= pivot) {
                [nums[i], nums[pIndex]] = [nums[pIndex], nums[i]];
                pIndex++;
            }
        }
        [nums[pIndex], nums[right]] = [nums[right], nums[pIndex]];
        return pIndex;
    };
    let left = 0, right = nums.length - 1, index = nums.length - k;
    while (left <= right) {
        const pivotIndex = partition(left, right);
        if (pivotIndex === index) {
            return nums[pivotIndex];
        } else if (pivotIndex < index) {
            left = pivotIndex + 1;
        } else {
            right = pivotIndex - 1;
        }
    }
};
方法2:最小堆
最小堆是一种完全二叉树,堆中每个节点的值都小于或等于其子节点的值。通过维护一个大小为 k 的最小堆,可以确保堆顶元素始终是当前最大的 k 个元素中的最小值。
实现步骤:
- 构建最小堆:使用数组实现最小堆。
 - 遍历数组:将每个元素添加到最小堆中。
 - 保持堆的大小为 k:如果堆的大小超过 k,则删除堆顶元素(最小元素)。
 - 返回堆顶元素:遍历结束后,堆顶元素即为第 k 大的元素。
 
class MinHeap {
    constructor() {
        this.heap = [];
    }
    size() {
        return this.heap.length;
    }
    peek() {
        return this.heap[0];
    }
    insert(value) {
        this.heap.push(value);
        this.heapifyUp();
    }
    remove() {
        if (this.size() === 1) {
            return this.heap.pop();
        }
        const root = this.heap[0];
        this.heap[0] = this.heap.pop();
        this.heapifyDown();
        return root;
    }
    heapifyUp() {
        let index = this.size() - 1;
        while (index > 0) {
            const parentIndex = Math.floor((index - 1) / 2);
            if (this.heap[parentIndex] <= this.heap[index]) break;
            [this.heap[parentIndex], this.heap[index]] = [this.heap[index], this.heap[parentIndex]];
            index = parentIndex;
        }
    }
    heapifyDown() {
        let index = 0;
        const length = this.size();
        const element = this.heap[0];
        while (true) {
            let leftChildIndex = 2 * index + 1;
            let rightChildIndex = 2 * index + 2;
            let leftChild, rightChild;
            let swap = null;
            if (leftChildIndex < length) {
                leftChild = this.heap[leftChildIndex];
                if (leftChild < element) {
                    swap = leftChildIndex;
                }
            }
            if (rightChildIndex < length) {
                rightChild = this.heap[rightChildIndex];
                if ((swap === null && rightChild < element) || (swap !== null && rightChild < leftChild)) {
                    swap = rightChildIndex;
                }
            }
            if (swap === null) break;
            [this.heap[index], this.heap[swap]] = [this.heap[swap], this.heap[index]];
            index = swap;
        }
    }
}
const findKthLargest = function(nums, k) {
    const minHeap = new MinHeap();
  
    for (let num of nums) {
        minHeap.insert(num);
        if (minHeap.size() > k) {
            minHeap.remove();
        }
    }
  
    return minHeap.peek();
};
// 示例测试
console.log(findKthLargest([3,2,1,5,6,4], 2)); // 输出: 5
console.log(findKthLargest([3,2,3,1,2,4,5,5,6], 4)); // 输出: 4
方法3:库函数
使用 JavaScript 的内置排序方法 Array.sort 和 slice 进行排序和切片,直接返回第 k 大的元素。虽然这种方法简单,但时间复杂度为 O(n log n),不满足题目要求的 O(n)。
代码实现:
const findKthLargest = function(nums, k) {
    nums.sort((a, b) => b - a);
    return nums[k - 1];
};
总结
本文介绍了三种方法来解决数组中的第 k 个最大元素问题:快速选择算法、最小堆和库函数。快速选择算法和最小堆都能在平均情况下达到 O(n) 的时间复杂度,而使用库函数的排序方法虽然简单,但时间复杂度为 O(n log n)。根据具体需求选择合适的方法进行实现。
          
            
          
评论区