二刷 LeetCode:215. 数组中的第 K 个最大元素 & 347. 前 K 个高频元素 复盘笔记

目录

[一、215. 数组中的第 K 个最大元素](#一、215. 数组中的第 K 个最大元素)

题目回顾

思路复盘

[方法 1:小顶堆(优先队列)](#方法 1:小顶堆(优先队列))

[方法 2:快速选择(优化版快排)](#方法 2:快速选择(优化版快排))

[易错点 & 二刷心得](#易错点 & 二刷心得)

[二、347. 前 K 个高频元素](#二、347. 前 K 个高频元素)

题目回顾

思路复盘

[方法 1:哈希表 + 小顶堆](#方法 1:哈希表 + 小顶堆)

[方法 2:桶排序](#方法 2:桶排序)

[易错点 & 二刷心得](#易错点 & 二刷心得)

[三、两道题的共性总结 & 二刷收获](#三、两道题的共性总结 & 二刷收获)


这两道题是堆 / 优先队列的经典考点,也是面试中高频的 Top K 问题,二刷时我们重点拆解思路、对比不同解法,顺便把易错点和通用模板总结清楚。


一、215. 数组中的第 K 个最大元素

题目回顾

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

思路复盘

这道题有两种主流解法:堆(优先队列) 和 快速选择,其中堆是面试中最常考的解法。

方法 1:小顶堆(优先队列)

核心思路:维护一个大小为 k 的小顶堆,遍历数组时:

  • 如果堆的大小小于 k,直接将元素加入堆中
  • 如果堆的大小等于 k,比较当前元素和堆顶元素:
    • 若当前元素大于堆顶元素,则弹出堆顶,加入当前元素
    • 否则跳过
  • 遍历结束后,堆顶元素就是第 k 个最大的元素

Java 代码实现

java

运行

复制代码
public int findKthLargest(int[] nums, int k) {
    PriorityQueue<Integer> minHeap = new PriorityQueue<>();
    for (int num : nums) {
        if (minHeap.size() < k) {
            minHeap.offer(num);
        } else {
            if (num > minHeap.peek()) {
                minHeap.poll();
                minHeap.offer(num);
            }
        }
    }
    return minHeap.peek();
}
方法 2:快速选择(优化版快排)

核心思路:利用快排的分区思想,每次将数组分为两部分,根据基准元素的位置,缩小查找范围:

  • 如果基准元素的位置等于 n-k,则基准元素就是答案
  • 如果基准元素的位置大于 n-k,则在左半部分继续查找
  • 如果基准元素的位置小于 n-k,则在右半部分继续查找

Java 代码实现

java

运行

复制代码
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 targetIndex) {
    int pivotIndex = partition(nums, left, right);
    if (pivotIndex == targetIndex) {
        return nums[pivotIndex];
    } else if (pivotIndex < targetIndex) {
        return quickSelect(nums, pivotIndex + 1, right, targetIndex);
    } else {
        return quickSelect(nums, left, pivotIndex - 1, targetIndex);
    }
}

private int partition(int[] nums, int left, int right) {
    int pivot = nums[right];
    int i = left;
    for (int j = left; j < right; j++) {
        if (nums[j] <= pivot) {
            swap(nums, i, j);
            i++;
        }
    }
    swap(nums, i, right);
    return i;
}

private void swap(int[] nums, int i, int j) {
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

易错点 & 二刷心得

  1. 堆的选择 :找第 k 个最大元素用小顶堆 ,找第 k 个最小元素用大顶堆,不要搞混。
  2. 时间复杂度:小顶堆的时间复杂度是 O (nlogk),快速选择的平均时间复杂度是 O (n),最坏情况是 O (n²),实际面试中优先推荐堆的解法。
  3. 边界处理 :快速选择时,目标索引是 n-k,而不是 k-1,注意区分从大到小和从小到大的排序。

二、347. 前 K 个高频元素

题目回顾

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按任意顺序返回答案。

思路复盘

这道题是哈希表 + 堆的经典组合,也可以用桶排序的方法解决。

方法 1:哈希表 + 小顶堆

核心思路:

  1. 先用哈希表统计每个元素的出现频率
  2. 维护一个大小为 k 的小顶堆,堆中元素按频率排序
  3. 遍历哈希表,将元素加入堆中,最终堆中的元素就是前 k 个高频元素

Java 代码实现

java

运行

复制代码
public int[] topKFrequent(int[] nums, int k) {
    // 1. 统计频率
    Map<Integer, Integer> freqMap = new HashMap<>();
    for (int num : nums) {
        freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
    }

    // 2. 小顶堆,按频率排序
    PriorityQueue<Map.Entry<Integer, Integer>> minHeap = 
        new PriorityQueue<>((a, b) -> a.getValue() - b.getValue());

    for (Map.Entry<Integer, Integer> entry : freqMap.entrySet()) {
        if (minHeap.size() < k) {
            minHeap.offer(entry);
        } else {
            if (entry.getValue() > minHeap.peek().getValue()) {
                minHeap.poll();
                minHeap.offer(entry);
            }
        }
    }

    // 3. 取出结果
    int[] result = new int[k];
    int index = 0;
    while (!minHeap.isEmpty()) {
        result[index++] = minHeap.poll().getKey();
    }
    return result;
}
方法 2:桶排序

核心思路:

  1. 先用哈希表统计每个元素的出现频率
  2. 创建一个桶数组,桶的索引表示频率,桶中存放对应频率的元素
  3. 从后往前遍历桶数组,收集前 k 个元素

Java 代码实现

java

运行

复制代码
public int[] topKFrequent(int[] nums, int k) {
    // 1. 统计频率
    Map<Integer, Integer> freqMap = new HashMap<>();
    for (int num : nums) {
        freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
    }

    // 2. 创建桶
    List<List<Integer>> buckets = new ArrayList<>();
    for (int i = 0; i <= nums.length; i++) {
        buckets.add(new ArrayList<>());
    }
    for (Map.Entry<Integer, Integer> entry : freqMap.entrySet()) {
        buckets.get(entry.getValue()).add(entry.getKey());
    }

    // 3. 从后往前收集结果
    int[] result = new int[k];
    int index = 0;
    for (int i = buckets.size() - 1; i >= 0 && index < k; i--) {
        List<Integer> bucket = buckets.get(i);
        for (int num : bucket) {
            result[index++] = num;
            if (index == k) {
                break;
            }
        }
    }
    return result;
}

易错点 & 二刷心得

  1. 堆的比较器 :在 Java 中,PriorityQueue 默认是小顶堆,所以自定义比较器时要按频率升序排列。
  2. 桶排序的适用场景:当元素频率分布较广时,桶排序的空间复杂度较高,但时间复杂度是 O (n),效率更高。
  3. 结果顺序:题目不要求返回元素的顺序,所以两种方法的结果都可以直接返回,不需要额外排序。

三、两道题的共性总结 & 二刷收获

  1. Top K 问题的通用解法 :
    • 小顶堆:时间复杂度 O (nlogk),空间复杂度 O (k),适用于大部分场景
    • 快速选择:平均时间复杂度 O (n),空间复杂度 O (1),适用于数组可修改的场景
    • 桶排序:时间复杂度 O (n),空间复杂度 O (n),适用于频率分布较集中的场景
  2. 堆的核心思想 :维护一个大小为 k 的堆,用堆顶元素过滤掉不需要的元素,最终堆中保留的就是前 k 个元素。
  3. 面试重点 :
    • 第 K 个最大元素:堆的实现和快速选择的优化思路,以及时间复杂度分析
    • 前 K 个高频元素:哈希表和堆的结合,桶排序的实现和适用场景
相关推荐
一隅论数智3 天前
给AI一张“业务概念地图“:本体如何从哲学走向企业智能
大数据·人工智能·经验分享·笔记·学习·学习方法·政务
深圳老胡3 天前
STM32F407 控制 L6470 步进电机驱动 —— 控制过程简介
笔记·stm32·单片机·嵌入式硬件·代码规范
旖旎夜光3 天前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控
Because_of_Her13 天前
并查集-听课笔记
笔记·算法·并查集
彧azz3 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
陈卫军老师3 天前
陈卫军:把口味写在一张纸上,店才稳得住
经验分享·笔记·流量运营
`流年づ3 天前
人工智能学习笔记 - 补充
人工智能·笔记·深度学习·学习
qeen873 天前
【Linux】操作系统之进程介绍(二)
linux·笔记·学习·进程
从零开始的嵌入式之旅3 天前
day47
arm开发·经验分享·笔记·嵌入式硬件
CoderYanger3 天前
A.每日一题:835. 图像重叠
java·开发语言·程序人生·leetcode·面试·职场和发展·学习方法