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

算法基础 典型题 堆

算法基础练习总结入口:我的算法地图

文章目录

    • 一、基本概念
    • 二、场景分析
    • 三、典型题目

一、基本概念

1、堆(Heap):是一种基于完全二叉树的数据结构,核心特性是 “堆序性”。 最大堆(或大顶堆) 父节点值 ≥ 子节点值;最小堆(或小顶堆)父节点值 ≤ 子节点值。堆的优势在于能高效获取和维护 “极值”(最大值或最小值),插入、删除操作的时间复杂度为 O (log n),是解决 “优先级处理、Top K 问题、动态中位数” 等场景的核心工具。
典型结构:完全二叉树(除最后一层外,每层节点全满;最后一层节点靠左排列),通常用数组实现(节省空间,便于索引计算)。索引关系(数组下标i):左孩子:2i + 1,右孩子:2i + 2,父节点:(i - 1) // 2。
在这里插入图片描述
2、堆排序:一种基于堆(Heap) 数据结构的高效排序算法,核心利用堆的 “堆序性”(最大堆的根节点是最大值,最小堆的根节点是最小值),通过 “建堆→提取堆顶→调整堆” 的循环,将无序数组逐步转换为有序数组。其时间复杂度稳定为O(n log n),且是原地排序(仅需 O (1) 额外空间),在需要稳定高效排序的场景中广泛应用。和几个典型快排算法比较:
在这里插入图片描述

二、场景分析

1、典型题目类型总结
在这里插入图片描述
2、技巧与注意事项总结
1)堆的选择(最大堆 vs 最小堆)
需频繁获取 “最大值”→ 最大堆(如任务调度);
需频繁获取 “最小值”→ 最小堆(如合并 K 个有序链表);
平衡场景(如中位数)→ 大小堆结合。
2)处理 “动态删除非堆顶元素”:
堆不支持高效删除任意元素(需 O (n) 查找),实际中常用 “延迟删除” 技巧:
用哈希表记录 “待删除元素” 的次数;
当堆顶元素是待删除元素时,才将其弹出并减少计数,避免提前处理。
3)堆的实现方式:
语言内置结构:C++ 的priority_queue(默认最大堆,最小堆需用greater<>)、Python 的heapq(默认最小堆,最大堆需取负);
自定义堆:通过数组 + 上浮 / 下沉函数实现,适合特殊场景(如带索引的堆)。
4)与其他数据结构的对比:
堆 vs 二叉搜索树(BST):堆获取极值 O (1),但不支持范围查询;BST 支持范围查询,但获取极值 O (log n);
堆 vs 哈希表:堆适合有序场景(如 Top K),哈希表适合快速查找但无序。

三、典型题目

215. 数组中的第K个最大元素
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 :输入: [3,2,1,5,6,4] 和 k = 2;输出: 5

    int findKthLargest(vector<int>& nums, int k) {priority_queue<int,vector<int>,greater<int>> small_heap;for(auto num:nums){if(small_heap.size() < k) {small_heap.push(num);            } else {if (num > small_heap.top()) {small_heap.pop();small_heap.push(num);}}}return small_heap.top();}

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

public:static bool cmp(pair<int, int>& m, pair<int, int>& n) {return m.second > n.second;}vector<int> topKFrequent(vector<int>& nums, int k) {// 思路:hash统计频次,然后用堆得到频次最高的K个// 1、hash统计频次, key为num值,value为次数unordered_map<int, int> freqhash;for (auto &num : nums) {freqhash[num]++;}//2、构建小顶堆 维持总数为K个, 元素1为值,元素2为次数priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(&cmp)> freqheap(cmp);for (auto &[num, count] : freqhash) {if (freqheap.size() == k) {if (freqheap.top().second < count) {freqheap.pop();freqheap.emplace(num, count);}} else {freqheap.emplace(num, count);}}// 3、构建结果vector <int> result;while (!freqheap.empty()) {result.emplace_back(freqheap.top().first);freqheap.pop();}return result;}

373. 查找和最小的 K 对数字
给定两个以 非递减顺序排列 的整数数组 nums1 和 nums2 , 以及一个整数 k 。定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2 。请找到和最小的 k 个数对 (u1,v1), (u2,v2) … (uk,vk) 。

class Solution {
public:vector<vector<int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) {// 思路:初始筛选出 “最可能是小和” 的候选数对,用最小堆动态维护这些候选// 简化技巧,利用数组 “越往后越大” 的特性,先抓最可能小的组合// 每次选完最小的,只扩展它的 “下一个可能”,既高效又不遗漏。// 最小堆的比较器,auto cmp = [&nums1, &nums2](const pair<int, int> & a, const pair<int, int> & b) {return nums1[a.first] + nums2[a.second] > nums1[b.first] + nums2[b.second];};int m = nums1.size();int n = nums2.size();vector<vector<int>> ans;// 最小堆:存储索引对(i,j),按数对和从小到大排序priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> pq(cmp);for (int i = 0; i < min(k, m); i++) {pq.emplace(i, 0); // (nums1[i], nums2[0])是nums1[i]能形成的最小和数对}while (k-- > 0 && !pq.empty()) {// 每次从堆顶取出和最小的数对索引(x, y)auto [x, y] = pq.top(); pq.pop();ans.emplace_back(initializer_list<int>{nums1[x], nums2[y]});// 补充新候选:当前数对是(x,y),下一个可能的候选是(x, y+1)// 无需补充(x+1, y),因为(x+1, 0)已在初始化时加入堆中,后续会逐步处理y递增的情况if (y + 1 < n) {pq.emplace(x, y + 1);}}return ans;}

295. 数据流的中位数
中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。
例如,[2,3,4] 的中位数是 3;[2,3] 的中位数是 (2 + 3) / 2 = 2.5;设计一个支持以下两种操作的数据结构:void addNum(int num) - 从数据流中添加一个整数到数据结构中。double findMedian() - 返回目前所有元素的中位数。

     /* 大顶堆 放小的一半,堆顶为最大值 */priority_queue<int, vector<int>, less<int>> low_heap;/* 小顶堆 放大的一半,堆顶为最小值 */priority_queue<int, vector<int>, greater<int>> high_heap;
public:// Adds a number into the data structure.void addNum(int num){if (low_heap.empty() == true) {low_heap.push(num);return;}if (low_heap.size() == high_heap.size()) {/* 两堆数量一样多,按实际大小判断放哪一堆里面 */if (num > low_heap.top()) {high_heap.push(num);} else {low_heap.push(num);               }} else if (high_heap.size() > low_heap.size()) {/* 大的数比较多,优先放low堆,放不进去就把high的top换过来 */if (num <= high_heap.top()) {low_heap.push(num);} else {low_heap.push(high_heap.top());high_heap.pop();high_heap.push(num);}} else {/* 小的数比较多,优先放high堆,放不进去就把low的top换过来 */if (num >= low_heap.top()) {high_heap.push(num);} else {high_heap.push(low_heap.top());low_heap.pop();low_heap.push(num);}}}double findMedian(){if (low_heap.size() == high_heap.size()) {return (low_heap.top() + high_heap.top()) / 2.0;} else if (low_heap.size() > high_heap.size()) {return low_heap.top();} else {return high_heap.top();}}

767. 重构字符串
给定一个字符串 s ,检查是否能重新排布其中的字母,使得两相邻的字符不同。返回 s 的任意可能的重新排列。若不可行,返回空字符串 “” 。

class Solution {
public:string reorganizeString(string s) {// 思路:贪心+最大堆// 如果某个字符出现次数过多(超过 (总长度+1)/2),则必然无法避免相邻重复// 如何获得字串,利用优先队列(最大堆)每次取出出现次数最多的两个字符,交替放置(避免相邻重复)// 减少计数后将剩余次数的字符重新入队,直到所有字符排列完成。if (s.length() < 2) {return s;}vector<int> counts(26, 0);int maxCount = 0;int length = s.length();// 可行性判断:若最大次数超过 (length+1)/2,必然有相邻重复for (int i = 0; i < length; i++) {char c = s[i];counts[c - 'a']++;maxCount = max(maxCount, counts[c - 'a']);}if (maxCount > (length + 1) / 2) {return "";}// 定义比较器:按字符出现次数降序排列(次数多的优先级高)auto cmp = [&](const char& letter1, const char& letter2) {return counts[letter1 - 'a']  < counts[letter2 - 'a'];};// 优先队列(最大堆):存储字符,按上述比较器排序priority_queue<char, vector<char>,  decltype(cmp)> queue{cmp};for (char c = 'a'; c <= 'z'; c++) {if (counts[c - 'a'] > 0) {queue.push(c);}}string sb = "";while (queue.size() > 1) {// 每次取两个不同的字符排列(避免相邻重复)char letter1 = queue.top(); queue.pop();char letter2 = queue.top(); queue.pop();sb += letter1;sb += letter2;int index1 = letter1 - 'a', index2 = letter2 - 'a';// 减少计数后,若仍有剩余,重新入队counts[index1]--;counts[index2]--;if (counts[index1] > 0) {queue.push(letter1);}if (counts[index2] > 0) {queue.push(letter2);}}// 若队列中剩余1个字符(字符串长度为奇数时),直接拼接(此时它的计数为1,且前一个字符与它不同)if (queue.size() > 0) {sb += queue.top();}return sb;}
};

912. 排序数组
给你一个整数数组 nums,请你将该数组升序排列。你必须在 不使用任何内置函数 的情况下解决问题,时间复杂度为 O(nlog(n)),并且空间复杂度尽可能小。

class Solution {// 调整堆:确保以i为根的子树满足最大堆性质void maxHeapify(vector<int>& nums, int i, int len) {for (; (i << 1) + 1 <= len;) {int lson = (i << 1) + 1;  // 左孩子索引:2*i + 1int rson = (i << 1) + 2;  // 右孩子索引:2*i + 2int large;                // 记录当前节点、左孩子、右孩子中最大值的索引if (lson <= len && nums[lson] > nums[i]) {large = lson;} else {large = i;}if (rson <= len && nums[rson] > nums[large]) {large = rson;}// 如果最大值不是当前节点,需要交换并继续下沉if (large != i) {swap(nums[i], nums[large]);i = large;} else {break;}}}// 构建最大堆,将无序数组转换为最大堆。void buildMaxHeap(vector<int>& nums, int len) {// 从最后一个非叶子节点开始,向前遍历并调整每个节for (int i = len / 2; i >= 0; --i) {maxHeapify(nums, i, len);}}void heapSort(vector<int>& nums) {int len = (int)nums.size() - 1;buildMaxHeap(nums, len);         // 第一步:构建最大堆for (int i = len; i >= 1; --i) {swap(nums[i], nums[0]);      // 交换堆顶(最大值)和当前堆尾(i位置),最大值归位len -= 1;                    // 堆的范围缩小(排除已归位的最大值)maxHeapify(nums, 0, len);    // 调整新堆顶,恢复最大堆性质}}
public:vector<int> sortArray(vector<int>& nums) {heapSort(nums);return nums;}
};
http://www.dtcms.com/a/453177.html

相关文章:

  • UVa 463 Polynomial Factorization
  • 老题新解|十进制转二进制
  • 数字信号处理 第八章(多采样率数字信号处理)
  • 网站制作农业免费封面设计在线制作生成
  • 多线程:三大集合类
  • html css js网页制作成品——化妆品html+css+js (7页)附源码
  • OpenAI战略转型深度解析:从模型提供商到全栈生态构建者的野望
  • 怎么做网站自动采集数据hao123设为主页官网下载
  • 重庆孝爱之家网站建设网站单页设计
  • 13、Linux 基本权限
  • k8s-ingress控制器
  • 【AI】深入 LangChain 生态:核心包架构解析
  • CodeBuddy Code + 腾讯混元打造“AI识菜通“
  • 记录踩过的坑-金蝶云·苍穹平台-杂七杂八
  • 【嵌入式原理系列-第11篇】半导体电子传输与PN结工作原理浅析
  • 磁力链接 网站怎么做的做网站多少钱西宁君博专注
  • 苹果RL4HS框架的技术原理
  • 在哪网站开发软件发视频的网址网址是什么?
  • 第74篇:AI+教育:个性化学习、智能辅导与虚拟教师
  • 2025 AI 落地元年:从技术突破到行业重构的实践图景
  • 《每日AI-人工智能-编程日报》--2025年10月7日
  • 公司销售泄密公司资料如何管控?信企卫文件加密软件深度分析
  • .NET+AI: (微家的AI开发框架)什么是内核记忆(Kernel Memory)?
  • 版本控制器 git(2)--- git 基本操作
  • 数字信号处理 第六章(IIR数字滤波器设计)
  • 辽宁专业网页设计免费建站正规seo服务商
  • 西安将军山网站建设wordpress评论模板怎么改
  • 抽象类定义
  • 基于sprigboot的农贸市场摊位管理系统(源码+论文+部署+安装)
  • 基于Flink的AB测试系统实现:从理论到生产实践