当前位置: 首页 > news >正文

leetcode-hot-100(堆)

1、 数组中的第 K 个最大元素

题目链接:数组中的第 K 个最大元素
题目描述:
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

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

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

解答

写在前面

要是没有时间复杂度的要求,实际上还是比较简单的,直接排序数组后,找到对应位置后即可:

class Solution {
public:int findKthLargest(vector<int>& nums, int k) {sort(nums.begin(), nums.end());return nums[nums.size() - k];}
};

由于题目是需要找寻排序后第 k 个最大的元素,而非第 k 个不同元素,因此一些细节方便就无需处理了。举例说明如下:
对于测试用例给定的数组:
输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4
排序后结果如下:[1, 2, 2, 3, 3, 4, 5, 5, 6]

按照一般的理解,如果需要找寻第 k 小的数字,一般重复的数字就算一个数字,也就是上述按道理来说应该是 3 ,但是题目说明了是找寻排序后第 k 个最大的元素,而非第 k 个不同元素。因此这才返回的结果是 4 。 我的代码也才可以编写成上面两行代码。

方法一:快速排序(官方解答)

class Solution {
public:int quicksort(vector<int>& nums, int l, int r, int k) {if (l == r)return nums[k];int partition = nums[l], i = l - 1, j = r + 1;// i 自增,j 自减,这是比较重要的,方式陷入死循环while (i < j) {doi++;while (nums[i] < partition);doj--;while (nums[j] > partition);if (i < j)swap(nums[i], nums[j]);}// 下面代码可能超时:因为数组可能会存在很多的重复元素,这样会导致 i 持续增加,这样快排效率就不是很高了// while (i <= j && nums[i] <= pivot)//     i++; // 从左找第一个 > pivot 的// while (i <= j && nums[j] >= pivot)//     j--; // 从右找第一个 < pivot 的// if (i < j)//     swap(nums[i], nums[j]);if (k <= j)return quicksort(nums, l, j, k);elsereturn quicksort(nums, j + 1, r, k);}int findKthLargest(vector<int>& nums, int k) {int len = nums.size();return quicksort(nums, 0, len - 1, len - k);}
};

关于上述注释起来的代码,为啥有错误,可以看下图的解析:在这里插入图片描述
或者写成下面比较容易理解的代码:

class Solution {
public:int partition(vector<int>& nums, int left, int right) {int pivot = nums[left];while (left < right) {while (nums[right] >= pivot && left < right) {right--;if (nums[right] == pivot)break;}nums[left] = nums[right];while (nums[left] <= pivot && left < right) {left++;if (nums[left] == pivot)break;}nums[right] = nums[left];}nums[left] = pivot;return left;}int qsort(vector<int>& nums, int left, int right, int k) {int pivot = partition(nums, left, right);if (pivot == k)return nums[pivot];else if (pivot < k)return qsort(nums, pivot + 1, right, k);elsereturn qsort(nums, left, pivot - 1, k);}int findKthLargest(vector<int>& nums, int k) {int len = nums.size();return qsort(nums, 0, len - 1, len - k);}
};

方法二:堆排序(官方解答)

堆排序分为最大堆和最小堆,这里需要求解的是 Kth 。 因此此题构建最大堆即可。

堆的概念和一般的方法可以参考:图解排序算法(三)之堆排序 和 堆排序详细图解(通俗易懂)

class Solution {
public:void maxHeapify(vector<int>& a, int i, int heapSize) {int l = i * 2 + 1, r = i * 2 + 2, largest = i;if (l < heapSize && a[l] > a[largest])largest = l;if (r < heapSize && a[r] > a[largest])largest = r;if (largest != i) {swap(a[i], a[largest]);// 递归地对被交换下去的节点进行堆化maxHeapify(a, largest, heapSize);}}// 从最后一个非叶子结点开始,构造一个最大堆void bulidMaxHeap(vector<int>& a, int heapSize) {for (int i = heapSize / 2 - 1; i >= 0; i--)maxHeapify(a, i, heapSize);}int findKthLargest(vector<int>& nums, int k) {int heapSize = nums.size();bulidMaxHeap(nums, heapSize); // 将数组变成一个最大堆// k = 1 时不需要执行for了,前面已经执行了for (int i = nums.size() - 1; i >= nums.size() - k + 1; i--) {// 把最大值放到数组中的最后一个元素去,然后对剩下的元素进行堆排序swap(nums[0], nums[i]);// 最后一个已经是最大值了,只需要对剩下的元素进行最大堆的求解即可--heapSize;maxHeapify(nums, 0, heapSize);}// 这里只需要求解 Kth。因此排序就不需要写 i>=0(这样就对整个数组进行升序排序了)// 想对整个数组排序的话,写成如下注释起来的代码即可// for (int i = nums.size() - 1; i >= 1; i--) {//     swap(nums[0], nums[i]);//     --heapSize;//     maxHeapify(nums, 0, heapSize);// }// return nums[nums.size() - k];return nums[0];}
};

2、 前 K 个高频元素

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

解答

方法一:借助哈希映射 + vector

先使用哈希映射统计相同元素出现的频次,然后将(数字,频次)对存储到 vector 中,然后再使用 lambda 表达式对数组进行降序排序, 然后存到一个 vector 中返回即可。

class Solution {
public:vector<int> topKFrequent(vector<int>& nums, int k) {// 步骤 1: 使用 unordered_map 统计每个数字的出现频次unordered_map<int, int> freqMap;for (int num : nums)freqMap[num]++;// 步骤 2: 将 map 中的 (数字, 频次) 对放入 vector 中,便于排序vector<pair<int, int>> freqVec(freqMap.begin(), freqMap.end());// 步骤 3: 按照频次 (pair 的 second) 从高到低排序// 使用 lambda 表达式定义比较规则sort(freqVec.begin(), freqVec.end(),[](const pair<int, int>& a, const pair<int, int>& b) {return a.second > b.second; // 降序排列});// // 上述 lambda 表达式等价于下面的形式// // 定义一个比较函数// bool compareByFrequency(const pair<int, int>& a,//                         const pair<int, int>& b) {//     return a.second > b.second; // 降序:频次高的在前// }// // 在 sort 中使用// sort(freqVec.begin(), freqVec.end(), compareByFrequency);// 步骤 4: 提取前 k 个高频数字vector<int> result;for (int i = 0; i < k; i++)result.push_back(freqVec[i].first); // 取出数字 (pair 的 first)return result;}
};

方法二:构建最大堆

思路和上一题一样,只是需要先使用哈希映射统计频次,然后再进行最大堆的构建

class Solution {
public:// 调整最大堆void maxHeapify(vector<pair<int, int>>& a, int i, int heapSize) {int l = 2 * i + 1, r = 2 * i + 2, largest = i;if (l < heapSize && a[l].second > a[largest].second)largest = l;if (r < heapSize && a[r].second > a[largest].second)largest = r;if (largest != i) {swap(a[i], a[largest]);maxHeapify(a, largest, heapSize);}}// 构建最大堆void bulidMaxHeap(vector<pair<int, int>>& a, int heapSize) {for (int i = heapSize / 2 - 1; i >= 0; i--)maxHeapify(a, i, heapSize);}// 找寻最大 K 频次的元素vector<int> topKFrequent(vector<int>& nums, int k) {// 统计频次unordered_map<int, int> occurrences;for (auto& num : nums)occurrences[num]++;// 转为 vector<pair<数字,频次>>vector<pair<int, int>> temp(occurrences.begin(), occurrences.end());int heapSize = temp.size();bulidMaxHeap(temp, heapSize);vector<int> result;for (int i = 0; i < k; i++) {result.push_back(temp[0].first);temp[0] = temp[heapSize - 1];heapSize--;if (heapSize > 0)maxHeapify(temp, 0, heapSize);}return result;}
};

方法三:构建最小堆

思路其实和最大堆差不多,只是为了熟悉一下最小堆的写法
主要需要注意一下:

  • 最小堆写法和最大堆某些地方是相反的
  • 我下面的代码是先对数组进行最小堆的降序排序,然后再取前 k 个元素。这样和上面的最大堆求法有些不一样。
    • 不能直接对 heapSize 进行操作,需要引入一个 i 进行操作
    • swap(temp[0], temp[i]); 而不是上述的 temp[0] = temp[heapSize - 1]; 因为后者会将最大值覆盖(也就是删除)。
class Solution {
public:// 调整最大堆void minHeapify(vector<pair<int, int>>& a, int i, int heapSize) {int l = 2 * i + 1, r = 2 * i + 2, minimum = i;if (l < heapSize && a[l].second < a[minimum].second)minimum = l;if (r < heapSize && a[r].second < a[minimum].second)minimum = r;if (minimum != i) {swap(a[i], a[minimum]);minHeapify(a, minimum, heapSize);}}// 构建最大堆void bulidMinHeap(vector<pair<int, int>>& a, int heapSize) {for (int i = heapSize / 2 - 1; i >= 0; i--)minHeapify(a, i, heapSize);}// 找寻最大 K 频次的元素vector<int> topKFrequent(vector<int>& nums, int k) {// 统计频次unordered_map<int, int> occurrences;for (auto& num : nums)occurrences[num]++;// 转为 vector<pair<数字,频次>>vector<pair<int, int>> temp(occurrences.begin(), occurrences.end());int heapSize = temp.size();bulidMinHeap(temp, heapSize);for (int i = heapSize - 1; i >= 1; i--) {swap(temp[0], temp[i]); // 把最小值放到位置 iminHeapify(temp, 0, i); // 调整前 i 个元素(堆大小为 i)}vector<int> result;for (int i = 0; i < k; i++) {result.push_back(temp[i].first);}return result;}
};

3、数据流的中位数

题目链接:数据流的中位数
题目描述:
在这里插入图片描述

解答

超时代码

我首先想到的是,构建一个最小堆,然后写一个 findnum 函数,找寻对应位置的数字,返回即可,但是代码居然超时了?????

class MedianFinder {
private:vector<int> result; // 存储所有数字int len;// 调整最小堆void minHeapify(int i, int heapSize) {int l = 2 * i + 1, r = 2 * i + 2, minimum = i;if (l < heapSize && result[l] < result[minimum])minimum = l;if (r < heapSize && result[r] < result[minimum])minimum = r;if (minimum != i) {swap(result[i], result[minimum]);minHeapify(minimum, heapSize);}}// 构建最小堆void buildMinHeap() {for (int i = len / 2 - 1; i >= 0; i--) {minHeapify(i, len);}}// 找第 k 小的数(k 从 1 开始)int findKthSmallest(int k) {// 临时拷贝,避免破坏原数组vector<int> temp = result;int heapSize = len;// 重建最小堆for (int i = heapSize / 2 - 1; i >= 0; i--) {minHeapifyOnArray(temp, i, heapSize);}// 删除前 k-1 个最小值for (int i = 0; i < k - 1; i++) {swap(temp[0], temp[heapSize - 1]);heapSize--;if (heapSize > 0) {minHeapifyOnArray(temp, 0, heapSize);}}return temp[0]; // 第 k 小}// 在指定数组上调用 minHeapifyvoid minHeapifyOnArray(vector<int>& arr, int i, int heapSize) {int l = 2 * i + 1, r = 2 * i + 2, minimum = i;if (l < heapSize && arr[l] < arr[minimum])minimum = l;if (r < heapSize && arr[r] < arr[minimum])minimum = r;if (minimum != i) {swap(arr[i], arr[minimum]);minHeapifyOnArray(arr, minimum, heapSize);}}public:MedianFinder() {// 构造函数}void addNum(int num) {result.push_back(num);len = result.size();// 插入后不需要立即建堆,等到 findMedian 时再建}double findMedian() {if (len == 0)return 0;// 计算中位数时,才建堆if (len % 2 == 1) {// 奇数:找第 (len+1)/2 小的数int k = (len + 1) / 2;return findKthSmallest(k);} else {// 偶数:找第 len/2 和 len/2 + 1 小的数int k1 = len / 2;int k2 = len / 2 + 1;int val1 = findKthSmallest(k1);int val2 = findKthSmallest(k2);return (val1 + val2) / 2.0;}}
};

在这里插入图片描述

GPT 写的大、小根堆解法

class MedianFinder {
private:priority_queue<int> maxHeap; // 较小的一半(最大堆)priority_queue<int, vector<int>, greater<int>> minHeap; // 较大的一半(最小堆)public:MedianFinder() {}void addNum(int num) {// 1. 先插入到 maxHeap(注意:maxHeap 存的是负数逻辑)if (maxHeap.empty() || num <= maxHeap.top()) {maxHeap.push(num);} else {minHeap.push(num);}// 2. 平衡两个堆if (maxHeap.size() > minHeap.size() + 1) {minHeap.push(maxHeap.top());maxHeap.pop();} else if (minHeap.size() > maxHeap.size() + 1) {maxHeap.push(minHeap.top());minHeap.pop();}}double findMedian() {if (maxHeap.size() > minHeap.size()) {return maxHeap.top(); // 奇数:maxHeap 多一个} else if (minHeap.size() > maxHeap.size()) {return minHeap.top(); // 奇数:minHeap 多一个} else {return (maxHeap.top() + minHeap.top()) / 2.0; // 偶数:取平均}}
};

或者下面的代码更加的简洁:

class MedianFinder {// 核心思想:维护两个堆,一个大根堆,一个小根堆// 保证:// 1、大根堆的最大值小于小根堆的最小值// 2、小根堆和大根堆中的数量差别最多相差 1// 这样建立的大小根堆:// 哪个根大返回其.top();// 一样大返回两堆的.top()之和/2.0
private:// 大根堆priority_queue<int, vector<int>, less<int>> small;// 小根堆priority_queue<int, vector<int>, greater<int>> large;public:MedianFinder() {}void addNum(int num) {small.push(num);if (small.size() && large.size() && small.top() > large.top()) {large.push(small.top());small.pop();}if (small.size() > large.size() + 1) {large.push(small.top());small.pop();}if (large.size() > small.size() + 1) {small.push(large.top());large.pop();}}double findMedian() {if (large.size() > small.size())return large.top();else if (small.size() > large.size())return small.top();else {return (large.top() + small.top()) / 2.0;}}
};
http://www.xdnf.cn/news/1409581.html

相关文章:

  • 分享一个实用的B站工具箱(支持音视频下载等功能)
  • Conda相关的用法
  • 业务逻辑漏洞类型及防范措施
  • 在实践中学Java(中)面向对象
  • 当 AI 开始 “筛选” 信息:算法偏见会加剧认知鸿沟吗?如何构建公平的 AI 生态?
  • 【算法笔记】算法归纳整理
  • (LeetCode 每日一题) 36. 有效的数独 (数组、哈希表)
  • 基于多模态大模型的PCB智能缺陷检测与分析
  • 人工智能学习:机器学习相关面试题(一)
  • 进程状态 —— Linux内核(Kernel)
  • 【动态规划】回文串问题
  • Wend看源码-marker(RAG工程-PDF文件解析)
  • R notes[2]
  • 鸿蒙Next文本组件全解析:输入框、富文本与属性字符串开发指南
  • Caffeine TimerWheel时间轮 深度解析:O(1)复杂度增删和触发时间事件
  • 李宏毅NLP-13-Vocoder
  • html添加水印
  • 2025年- H103-Lc211--3090. 每个字符最多出现两次的最长子字符串(双指针)--Java版
  • leetcode 268 丢失的数字
  • AG32 Nano开发板的烧录与调试工具(二)
  • 【开题答辩全过程】以 基于vue+springboot的校园疫情管理系统的设计与实现为例,包含答辩的问题和答案
  • 异步编程与面向对象知识总结
  • 家庭全光组网高温故障深度分析与散热重构全记录
  • 【图论】Graph.jl 核心函数
  • 一种使用 Java / Kotlin 编写检测BT种子的磁力链接是否有可用 peers 的程序
  • 扩展:如何设计与实现一个微服务架构下的跨服务异常处理适配器?
  • linux修改权限命令chmod
  • sunset: twilight靶场
  • 利用ms-swift微调和百炼平台微调大模型
  • FTP - 学习/实践