大顶堆实战:C++ priority_queue解决LeetCode 1046最后一块石头的重量
这次我们来看力扣LeetCode第1046题“最后一块石头的重量”。这道题本身并不复杂但它是一个绝佳的窗口让我们能深入理解“大顶堆”Max Heap这一数据结构的核心思想与实战应用。很多人在学习数据结构时感觉理论枯燥做题时又不知如何下手这正是“知”与“行”的脱节。本文将带你用C的STL标准模板库工具直接解决这道题并在此过程中让你真正领悟如何将数据结构的理论知识转化为解决实际问题的代码能力。本文将重点关注如何用最直接的方式解题并深入分析背后的原理。我们会先看题目能不能用大顶堆解决再详细拆解怎么用。内容涵盖从问题理解、核心数据结构选择priority_queue、C代码实现、复杂度分析到如何将这种解题思维迁移到其他问题上。无论你是正在准备算法面试还是希望巩固数据结构知识这篇文章都能提供一条清晰的“知行合一”的实践路径。1. 核心能力速览大顶堆与力扣1046题在深入代码之前我们先快速把握解决这个问题的核心工具和思路。能力项说明问题类型模拟、贪心、堆优先队列应用核心数据结构大顶堆 (Max Heap)STL实现工具std::priority_queueT(默认即为大顶堆)时间复杂度O(n log n)其中 n 为石头数量空间复杂度O(n)解题关键每次从堆中取出两个最重的石头进行碰撞将剩余重量如果有放回堆中重复此过程直至堆中元素少于2个。适合读者算法初学者、准备技术面试者、希望理解堆数据结构实战应用的学习者这道题完美诠释了“选择合适的数据结构问题就解决了一半”。大顶堆能让我们在O(1)时间内获取当前最重的石头这是高效解题的关键。2. 适用场景与使用边界2.1 这道题解决了什么问题力扣1046题描述了一个简单的物理模拟过程有一堆石头每块石头的重量都是正整数。每一回合选出两块最重的石头进行碰撞。假设石头重量分别为x和y且x y碰撞结果如下如果x y两块石头都会完全粉碎。如果x ! y重量为x的石头会粉碎重量为y的石头新重量为y - x。 重复这个过程直到最多剩下一块石头。返回此石头的重量如果没有石头剩下就返回0。这个问题本质上是一个持续动态获取最大值并更新集合的过程。手动排序或每次遍历查找最大值都会导致超时或代码低效而大顶堆正是为此类场景量身定做。2.2 大顶堆为什么是首选动态维护最值堆可以在插入和删除元素时以O(log n)的代价维护最大或最小元素在堆顶避免每次O(n)的查找。操作高效对于本题的模拟过程取两个最大可能插入一个差值堆的操作序列两次pop一次push在时间复杂度上是最优的。STL直接支持C的std::priority_queue开箱即用无需手动实现堆的调整算法让开发者能聚焦于问题逻辑本身。2.3 思维迁移哪些问题也适用这种模式当你遇到问题描述中包含“每次取最大/最小的几个元素进行处理并可能将处理结果放回”时就应该立即想到堆优先队列。典型场景包括任务调度总是先执行优先级最高的任务。合并K个有序链表每次从K个链表的头节点中取最小的。数据流的中位数使用一个大顶堆和一个小顶堆共同维护。哈夫曼编码每次合并频率最小的两个节点。理解1046题的解法就掌握了解决这一类问题的通用钥匙。3. 环境准备与前置条件在开始编码之前确保你的开发环境已经就绪。本题不涉及复杂的依赖或硬件要求重点在于编程语言和工具链。3.1 软件环境准备C编译器支持C11或更高版本。常见选择有GCC(MinGW-w64) 适用于Windows可通过MSYS2或MinGW安装、Linux和macOS。Clang 在macOS和Linux上常见。Microsoft Visual C 在Windows上使用Visual Studio或VS Code配合MSVC工具链。代码编辑器或IDEVisual Studio Code 轻量级需安装C/C扩展。CLion JetBrains出品功能强大的跨平台C IDE。Visual Studio Windows平台功能最全面的IDE。调试工具 熟悉使用IDE内置调试器或GDB/LLDB进行单步调试、查看变量对于理解程序运行过程至关重要。3.2 知识前置条件基础C语法 了解vector、循环、条件判断等。STL容器基本概念 知道vector、queue等容器的用途。堆Heap的概念 至少理解堆是一种特殊的完全二叉树父节点的值总是大于大顶堆或小于小顶堆其子节点的值。无需手动实现但需理解其特性。3.3 力扣平台准备如果你选择在力扣官网直接解题拥有一个力扣账户。在题目页面语言选择C。系统会自动提供一个函数签名作为起点你只需要在函数体内实现逻辑。4. 核心解法拆解与C实现现在我们进入核心环节如何用C STL中的priority_queue来解决这个问题。4.1 解题思路步骤化初始化大顶堆 将所有石头的重量放入一个大顶堆中。在C中std::priority_queueint默认就是大顶堆。模拟碰撞循环 当堆中的石头数量大于1时持续进行以下操作 a.取出最重的两块石头 通过top()和pop()操作获取并移除堆顶元素即当前最重的石头连续进行两次。 b.计算碰撞结果 比较两块石头的重量。 c.处理剩余重量 如果碰撞后剩下重量即y - x且y x将该重量作为新石头放回堆中push操作。返回最终结果 循环结束后如果堆为空返回0。如果堆中剩下一块石头返回该石头的重量。4.2 完整C代码实现#include queue #include vector using namespace std; class Solution { public: int lastStoneWeight(vectorint stones) { // 1. 初始化大顶堆 priority_queueint max_heap; for (int weight : stones) { max_heap.push(weight); } // 2. 模拟碰撞过程 while (max_heap.size() 1) { // 取出最重的两块石头 int stone1 max_heap.top(); max_heap.pop(); int stone2 max_heap.top(); max_heap.pop(); // 碰撞并处理剩余部分 if (stone1 ! stone2) { // 注意stone1和stone2是从堆顶取出的但stone1不一定是较大的那个。 // 因为默认是大顶堆先取出的是最大值后取出的是次大值。 // 所以这里我们直接计算差值差值一定为正。 int newWeight stone1 - stone2; // 或者 abs(stone1 - stone2) max_heap.push(newWeight); } // 如果相等则两块石头都粉碎无需任何操作 } // 3. 返回最终结果 return max_heap.empty() ? 0 : max_heap.top(); } };4.3 代码逐行解析priority_queueint max_heap; 声明一个存储int类型的大顶堆。模板默认为lessint即最大元素在顶部。for (int weight : stones) { max_heap.push(weight); } 使用范围for循环将输入数组的所有元素依次插入堆中。每次push操作的时间复杂度为O(log n)。while (max_heap.size() 1) 循环继续的条件是至少还有两块石头可以碰撞。int stone1 max_heap.top(); max_heap.pop(); 这是获取并移除堆顶元素的标准操作。top()获取但不移除pop()移除但不返回。必须分两步。if (stone1 ! stone2) 判断两块石头重量是否相等。注意由于我们先取stone1再取stone2且堆顶是最大值因此stone1 stone2。所以stone1 - stone2一定非负无需使用abs。max_heap.push(newWeight); 将碰撞后剩余的新石头重量放回堆中。return max_heap.empty() ? 0 : max_heap.top(); 三目运算符简洁地处理了可能返回0或剩余石头重量的情况。5. 复杂度分析与性能观察理解算法效率是“知行合一”的重要部分。我们不仅要知道代码怎么写还要知道它为什么好。5.1 时间复杂度O(n log n)建堆操作 将n个元素依次插入空堆每次插入是O(log n)总成本约为O(n log n)。更精确的建堆方式可以从底向上heapify复杂度为O(n)但STL的priority_queue构造函数通常采用依次插入的方式。模拟碰撞过程 在最坏情况下每次碰撞都产生一个新石头即每次重量都不相等。那么总共需要进行(n-1)次碰撞每次碰撞涉及两次popO(log n)和一次pushO(log n)。因此循环内的操作总时间复杂度也是O(n log n)。主导项 O(n log n)是主导项因此算法总时间复杂度为O(n log n)。5.2 空间复杂度O(n)我们使用了一个priority_queue来存储所有石头在最坏情况下需要存储n个元素。因此空间复杂度为O(n)。5.3 与暴力排序法的对比一种直观的暴力解法是每次碰撞前都对当前石头数组进行排序例如使用sortO(n log n)然后取最大的两个。这样每次循环都需要O(n log n)的排序总时间复杂度将高达O(n^2 log n)在n较大时如力扣的测试用例极易超时。大顶堆的方案将“维护有序性”的成本从每次O(n log n)降到了每次O(log n)是质的飞跃。6. 功能测试与效果验证编写完代码后必须进行测试来验证其正确性。我们设计几个典型的测试用例。6.1 测试用例设计// 可以在本地main函数中测试也可以在力扣的自定义测试用例中验证 int main() { Solution sol; vectorint test1 {2,7,4,1,8,1}; // 经典示例 cout Test1 [2,7,4,1,8,1]: sol.lastStoneWeight(test1) endl; // 应输出 1 vectorint test2 {1}; // 单块石头 cout Test2 [1]: sol.lastStoneWeight(test2) endl; // 应输出 1 vectorint test3 {1, 1}; // 两块相同石头 cout Test3 [1,1]: sol.lastStoneWeight(test3) endl; // 应输出 0 vectorint test4 {10,10,10,10}; // 多块相同石头 cout Test4 [10,10,10,10]: sol.lastStoneWeight(test4) endl; // 应输出 0 vectorint test5 {9,3,2,10}; // 随机顺序 // 过程: (10,9)-剩1, 堆变为[3,2,1]; (3,2)-剩1,堆变为[1,1]; (1,1)-剩0。 cout Test5 [9,3,2,10]: sol.lastStoneWeight(test5) endl; // 应输出 0 return 0; }6.2 测试执行与结果判断编译运行 将Solution类和测试代码放在同一个文件中编译并运行。观察输出 程序应依次输出1,1,0,0,0。调试观察 对于复杂用例如test1可以在循环中打印堆的状态直观观察碰撞过程加深对算法流程的理解。while (max_heap.size() 1) { // ... 取出stone1, stone2 ... cout 碰撞: stone1 和 stone2; if (stone1 ! stone2) { max_heap.push(newWeight); cout , 放入新石头: newWeight; } cout endl; // 可以打印当前堆内容需要额外操作因为priority_queue不能直接遍历 }6.3 力扣提交验证将Solution类的代码复制到力扣题目编辑器中点击“执行代码”或“提交”。系统会运行多组隐藏的测试用例。如果所有用例都通过你会看到“通过”的提示并附有运行时间和内存消耗的统计。这是最终的验收标准。7. 常见问题与排查方法在实现和测试过程中你可能会遇到以下问题。问题现象可能原因排查方式解决方案编译错误‘priority_queue’ was not declared未包含必要的头文件。检查代码开头是否#include queue。添加#include queue。运行时错误或逻辑错误结果不对1. 错误理解了top()和pop()的顺序。2. 碰撞后处理逻辑有误比如把差值算反了。1. 使用小型测试用例如{2,2}单步调试。2. 在碰撞逻辑后打印stone1,stone2和newWeight。1. 牢记top()获取值pop()移除值两者需分开调用。2. 确认stone1是第一次pop出来的stone2是第二次pop出来的且stone1 stone2所以剩余重量是stone1 - stone2。时间超限 (TLE)使用了低效算法如每次碰撞前都排序。审查代码确认是否使用了priority_queue。必须使用堆优先队列来维护最大值确保时间复杂度为O(n log n)。内存消耗过大可能使用了额外的、不必要的容器来复制数据。检查是否除了priority_queue外还保留了原始的stones向量副本。算法只需要一个priority_queue输入向量stones可以直接遍历使用无需额外拷贝。对于{1}的输入返回0循环条件或最终返回逻辑有误。测试单元素输入跟踪代码流程。循环条件是while (max_heap.size() 1)对于单元素不会进入循环。最终返回前应判断堆是否为空。使用return max_heap.empty() ? 0 : max_heap.top();。8. 扩展思考与最佳实践解决一个问题后进行扩展思考是提升能力的关键。8.1 如果要求返回碰撞过程记录怎么办有时面试官会问如何记录每一次碰撞这需要我们在模拟过程中保存状态。可以定义一个结构体或使用pair来记录。vectorpairint, int collisionRecord; // 记录每次碰撞的两块石头重量 while (max_heap.size() 1) { int y max_heap.top(); max_heap.pop(); int x max_heap.top(); max_heap.pop(); collisionRecord.emplace_back(y, x); // 记录 if (y x) { max_heap.push(y - x); } } // 最终collisionRecord保存了所有碰撞历史8.2 如何用小顶堆解决这个问题虽然本题用大顶堆最直观但使用小顶堆通过传入greaterint比较器也可以解决只是逻辑上需要一点转换将所有石头重量的负值放入小顶堆。这样绝对值最大的负数即原最大的正数会在堆顶。priority_queueint, vectorint, greaterint min_heap; // 小顶堆 for (int w : stones) { min_heap.push(-w); // 存入负值 } while (min_heap.size() 1) { int stone1 -min_heap.top(); min_heap.pop(); // 取出并转回正值 int stone2 -min_heap.top(); min_heap.pop(); if (stone1 ! stone2) { min_heap.push(-(stone1 - stone2)); // 将差值的负值存回 } } return min_heap.empty() ? 0 : -min_heap.top();这种方法有助于理解堆的比较器本质。8.3 工程实践中的建议优先使用STL 在面试或实际项目中除非有特殊性能定制需求否则应优先使用std::priority_queue而非手写堆。它经过充分测试正确且高效。理解抽象而非死记 记住“动态求极值用堆”而不是死记1046题的代码。遇到新问题时先抽象出核心操作再匹配数据结构。复杂度分析是必备技能 写完代码后养成分析时间、空间复杂度的习惯并能向他人清晰解释。测试驱动 先写几个简单的测试用例边界条件、特殊情况再实现代码最后用更复杂的用例验证。9. 总结力扣1046题“最后一块石头的重量”是一个绝佳的数据结构教学案例。它表面上是一个简单的模拟题但深层次考察的是你是否能为“频繁获取最大值并更新集合”这一核心操作选择最高效的数据结构——大顶堆。通过本文的拆解我们不仅得到了一个简洁的Cpriority_queue解法更完成了一次“知行合一”的实践知 理解大顶堆的特性快速取最值、插入删除O(log n)。行 应用std::priority_queue解决具体问题完成从问题分析、代码实现、测试验证到复杂度分析的完整闭环。掌握这种从问题特征到数据结构选择的思维模式远比背下十道题的答案更有价值。当下次遇到“数据流的中位数”、“任务调度器”、“合并K个排序链表”等问题时你会自然而然地想到“这里是不是该用堆了”建议你将此题的代码和思路作为模板收藏并尝试用同样的思维去解决力扣第215题“数组中的第K个最大元素”堆的另一个经典应用巩固这一重要的数据结构实战能力。

相关新闻

构建可靠数据分析Agent:从LLM原理到工程实践

构建可靠数据分析Agent:从LLM原理到工程实践

1. 项目概述:当数据分析Agent“答非所问”时,我们在面对什么?最近在和一些做AI应用开发的朋友交流,尤其是那些在尝试构建数据分析Agent(Analytics Agent)的团队,一个高频的抱怨是:“…

2026/9/20 12:44:09 阅读更多 →
python的工业过程控制场景模拟第一百零三篇:仓储机器人库位优先算法,高频取用物料放置靠近出入口,缩短搬运距离。

python的工业过程控制场景模拟第一百零三篇:仓储机器人库位优先算法,高频取用物料放置靠近出入口,缩短搬运距离。

仓储机器人库位优化算法 —— 基于存取频次的动态热区调度 “那年电商大促,仓库里最忙的几台 AGV 每天要在货架间跑 80km,结果发现爆款商品全被放在最角落。后来我们用频次-距离加权算法重构了库位分配策略,把高频物料‘吸’到出入口附近&…

2026/9/11 11:05:54 阅读更多 →
python的工业过程控制场景模拟第一百零二篇:机械臂防碰撞检测算法,实时扫描周边管道,执行器,预判碰撞风险提前停机。

python的工业过程控制场景模拟第一百零二篇:机械臂防碰撞检测算法,实时扫描周边管道,执行器,预判碰撞风险提前停机。

机械臂防碰撞检测算法 —— 基于实时距离场与轨迹预判的安全停机系统 “那年核岛检修,机械臂在盲区内蹭到了蒸汽管道,直接触发了辐射泄漏报警。后来我们在控制系统中植入了实时距离场(SDF) 前瞻预测的双层防护,让机械臂…

2026/9/18 19:26:00 阅读更多 →

最新新闻

搞懂导数公式及运算法则面试必问避坑指南

搞懂导数公式及运算法则面试必问避坑指南

搞懂导数公式及运算法则面试必问避坑指南 面对满屏红色的 Stack Overflow 报错,你是不是瞬间懵了?别慌,这通常是基础概念没吃透导致的逻辑崩溃,也是技术面试中“面试必问”的高频雷区。很多开发者在实现数值微分或优化算法时,往往因为对…

2026/9/22 7:01:32 阅读更多 →
弹弹堂高抛计算器源码拆解一文搞懂物理引擎

弹弹堂高抛计算器源码拆解一文搞懂物理引擎

弹弹堂高抛计算器源码拆解一文搞懂物理引擎 很多开发者卡在“懂语法但不会搭项目”的瓶颈,手里全是零散的代码片段,拼不出完整功能。其实只要看透底层逻辑,这类工具的开发思路就清晰了。今天咱们就 一文搞懂…

2026/9/22 7:01:32 阅读更多 →
3个真实案例教你图解koobeei50原理,避开跨省转介与执业法律大坑

3个真实案例教你图解koobeei50原理,避开跨省转介与执业法律大坑

3个真实案例教你图解koobeei50原理,避开跨省转介与执业法律大坑 刚接手一个跨省医疗数据对接项目,前端同事扔来一段从CSDN复制的 koobeei50 调用代码。代码看着挺像那么回事,变量名也很规范,但一运行直接报错:…

2026/9/22 7:01:32 阅读更多 →
切换快捷键总失效?3个常见坑点与修复方案避坑指南

切换快捷键总失效?3个常见坑点与修复方案避坑指南

切换快捷键总失效?3个常见坑点与修复方案避坑指南 看了一堆教程还是不会写项目?别急,问题可能不在逻辑,而在你连 切换快捷键 都没调对。很多应届生在本地调试时,明明代码逻辑没错,一跑起来就卡死或者响应迟钝,最后发现是 IDE 的 切换快捷键…

2026/9/22 7:01:32 阅读更多 →
3分钟一文搞懂肿瘤异质性,面试原理不再卡壳

3分钟一文搞懂肿瘤异质性,面试原理不再卡壳

3分钟一文搞懂肿瘤异质性,面试原理不再卡壳 面试被问原理答不上来?别慌,很多应届生在算法或生物信息面试中,一听到“肿瘤异质性”就脑子一片空白,只能干巴巴地背定义。其实,只要你能把复杂的生物学现象拆解成数据流和计算逻辑, 一文搞懂…

2026/9/22 7:01:32 阅读更多 →
图解原理:xinai手写实现避坑指南,3招搞定跑不通代码

图解原理:xinai手写实现避坑指南,3招搞定跑不通代码

图解原理:xinai手写实现避坑指南,3招搞定跑不通代码 复制来的 xinai 相关代码,跑不通?别慌,这通常是环境配置或底层逻辑理解偏差导致的。很多应届生在面试突击阶段,遇到这种“看似简单实则坑多”的面试题,往往因为缺乏对【图解原理】的深…

2026/9/22 7:00:32 阅读更多 →

日新闻

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天 配置环境就卡半天?别怪机器慢,多半是你没选对工具链。在Java、Go或Python的项目现场, 手写实现…

2026/9/22 0:00:41 阅读更多 →
剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑 面试被问原理答不上来,是不是常态?别慌。很多开发者对着 GitHub 开源仓库里的代码发呆,看似简单实则暗藏玄机。今天这份【剑帝加点】速查手册,直接带你拆解核心实现,把面试必考的原理讲透。…

2026/9/22 0:00:41 阅读更多 →
手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优 复制来的代码跑不通不知道怎么调?别慌,这种“复制粘贴地狱”在开发圈太常见了。尤其是做 图片压缩网站…

2026/9/22 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/22 4:32:41 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/22 4:38:57 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/21 4:51:05 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/22 2:43:42 阅读更多 →