LeetCode第215题_数组中的第K个最大元素

LeetCode 第215题:数组中的第K个最大元素

题目描述

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

难度

中等

题目链接

点击在LeetCode中查看题目

示例

示例 1:

输入: [3,2,1,5,6,4], k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4

提示

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

解题思路

解决这个问题的方法有多种,最直观的是排序后取第k大的元素,但题目要求我们实现时间复杂度为O(n)的算法。主要有以下几种解法:

方法一:快速选择算法(Quick Select)

快速选择是基于快速排序的选择算法,可以在平均时间复杂度O(n)内找到数组中第k大的元素。它不需要对整个数组进行排序,只需要确定第k大元素的位置即可。

算法步骤:

  1. 选取一个pivot(基准元素)
  2. 将数组分为两部分:大于pivot的部分和小于pivot的部分
  3. 根据pivot的位置和k的关系决定在哪部分继续寻找
  4. 递归重复以上步骤,直到找到第k大的元素

该算法在平均情况下时间复杂度为O(n),最坏情况下为O(n²)。

方法二:堆排序(Heap Sort)

使用最小堆(Min Heap)维护k个最大的元素,遍历一次数组后,堆顶即为第k大的元素。

算法步骤:

  1. 创建一个大小为k的最小堆
  2. 遍历数组,对于每个元素:
    • 如果堆的大小小于k,将元素加入堆
    • 如果元素大于堆顶,移除堆顶并加入当前元素
  3. 遍历结束后,堆顶元素即为第k大的元素

该算法的时间复杂度为O(n log k),空间复杂度为O(k)。

方法三:计数排序

如果数组元素的范围有限,可以使用计数排序。但这种方法适用性有限。

在本题中,我们将主要实现快速选择算法和堆排序方法,因为它们更加通用,也是常见的面试解法。

代码实现

C# 实现

public class Solution {
    // 快速选择算法
    public int FindKthLargest(int[] nums, int k) {
        return QuickSelect(nums, 0, nums.Length - 1, nums.Length - k);
    }
    
    private int QuickSelect(int[] nums, int left, int right, int kSmallest) {
        if (left == right) return nums[left];
        
        Random random = new Random();
        int pivotIndex = left + random.Next(right - left);
        
        pivotIndex = Partition(nums, left, right, pivotIndex);
        
        if (kSmallest == pivotIndex) {
            return nums[kSmallest];
        } else if (kSmallest < pivotIndex) {
            return QuickSelect(nums, left, pivotIndex - 1, kSmallest);
        } else {
            return QuickSelect(nums, pivotIndex + 1, right, kSmallest);
        }
    }
    
    private int Partition(int[] nums, int left, int right, int pivotIndex) {
        int pivotValue = nums[pivotIndex];
        // 将pivot移到末尾
        Swap(nums, pivotIndex, right);
        int storeIndex = left;
        
        // 将小于pivot的元素移到左侧
        for (int i = left; i < right; i++) {
            if (nums[i] < pivotValue) {
                Swap(nums, storeIndex, i);
                storeIndex++;
            }
        }
        
        // 将pivot放到最终位置
        Swap(nums, storeIndex, right);
        
        return storeIndex;
    }
    
    private void Swap(int[] nums, int a, int b) {
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
    
    // 堆排序方法
    public int FindKthLargestWithHeap(int[] nums, int k) {
        // 使用优先队列(最小堆)
        PriorityQueue<int, int> minHeap = new PriorityQueue<int, int>();
        
        foreach (int num in nums) {
            minHeap.Enqueue(num, num);
            if (minHeap.Count > k) {
                minHeap.Dequeue();
            }
        }
        
        return minHeap.Peek();
    }
}

Python 实现

import heapq
import random

class Solution:
    # 快速选择算法
    def findKthLargest(self, nums: List[int], k: int) -> int:
        return self.quickSelect(nums, 0, len(nums) - 1, len(nums) - k)
    
    def quickSelect(self, nums, left, right, k_smallest):
        if left == right:
            return nums[left]
        
        # 随机选择pivot
        pivot_index = random.randint(left, right)
        
        # 分区
        pivot_index = self.partition(nums, left, right, pivot_index)
        
        if k_smallest == pivot_index:
            return nums[k_smallest]
        elif k_smallest < pivot_index:
            return self.quickSelect(nums, left, pivot_index - 1, k_smallest)
        else:
            return self.quickSelect(nums, pivot_index + 1, right, k_smallest)
    
    def partition(self, nums, left, right, pivot_index):
        pivot_value = nums[pivot_index]
        # 将pivot移到末尾
        nums[pivot_index], nums[right] = nums[right], nums[pivot_index]
        store_index = left
        
        # 将小于pivot的元素移到左侧
        for i in range(left, right):
            if nums[i] < pivot_value:
                nums[store_index], nums[i] = nums[i], nums[store_index]
                store_index += 1
        
        # 将pivot放到最终位置
        nums[store_index], nums[right] = nums[right], nums[store_index]
        
        return store_index
    
    # 堆排序方法
    def findKthLargestWithHeap(self, nums: List[int], k: int) -> int:
        # 使用最小堆
        min_heap = []
        for num in nums:
            heapq.heappush(min_heap, num)
            if len(min_heap) > k:
                heapq.heappop(min_heap)
        
        return min_heap[0]

C++ 实现

class Solution {
public:
    // 快速选择算法
    int findKthLargest(vector<int>& nums, int k) {
        return quickSelect(nums, 0, nums.size() - 1, nums.size() - k);
    }
    
private:
    int quickSelect(vector<int>& nums, int left, int right, int kSmallest) {
        if (left == right) return nums[left];
        
        // 随机选择pivot
        int pivotIndex = left + rand() % (right - left + 1);
        
        pivotIndex = partition(nums, left, right, pivotIndex);
        
        if (kSmallest == pivotIndex) {
            return nums[kSmallest];
        } else if (kSmallest < pivotIndex) {
            return quickSelect(nums, left, pivotIndex - 1, kSmallest);
        } else {
            return quickSelect(nums, pivotIndex + 1, right, kSmallest);
        }
    }
    
    int partition(vector<int>& nums, int left, int right, int pivotIndex) {
        int pivotValue = nums[pivotIndex];
        // 将pivot移到末尾
        swap(nums[pivotIndex], nums[right]);
        int storeIndex = left;
        
        // 将小于pivot的元素移到左侧
        for (int i = left; i < right; i++) {
            if (nums[i] < pivotValue) {
                swap(nums[storeIndex], nums[i]);
                storeIndex++;
            }
        }
        
        // 将pivot放到最终位置
        swap(nums[storeIndex], nums[right]);
        
        return storeIndex;
    }
    
public:
    // 堆排序方法
    int findKthLargestWithHeap(vector<int>& nums, int k) {
        // 使用最小堆
        priority_queue<int, vector<int>, greater<int>> minHeap;
        
        for (int num : nums) {
            minHeap.push(num);
            if (minHeap.size() > k) {
                minHeap.pop();
            }
        }
        
        return minHeap.top();
    }
};

性能分析

各语言实现的性能对比:

实现语言执行用时内存消耗特点
C#100 ms40.2 MB使用随机化pivot可以避免最坏情况
Python64 ms16.8 MB内置的heapq库使堆操作简便
C++8 ms10.1 MB性能最优,内存消耗较小

补充说明

代码亮点

  1. 快速选择算法通过随机选择pivot,大大降低了遇到最坏情况的概率
  2. 堆排序方法使用了语言内置的优先队列/堆结构,代码简洁高效
  3. 两种方法都只需要部分排序,比完全排序更有效率
  4. C++ 实现使用了STL库的priority_queue,Python使用了heapq,C#使用了PriorityQueue,充分利用了语言特性

优化方向

  1. 对于大数据量,可以考虑使用三数取中法(Median of Three)选择pivot
  2. 对于数据范围有限的情况,可以考虑使用计数排序
  3. 对于多次查询,可以考虑先构建一个排序数组,然后直接返回结果
  4. 可以使用迭代而非递归实现快速选择,以避免潜在的栈溢出问题

解题难点

  1. 理解快速选择算法的核心思想和实现细节
  2. 正确处理分区(partition)操作,确保元素正确归位
  3. 转换思维,将第k大转换为第(n-k)小的问题
  4. 处理各种边界情况,如k=1或k=数组长度

常见错误

  1. 忘记将问题从"第k大"转换为"第(n-k)小"
  2. 分区函数实现错误,导致无法正确找到目标元素
  3. 递归条件设置不当,导致无限递归或错误结果
  4. 忽略随机选择pivot的重要性,在某些情况下导致性能下降

相关题目

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值