如何用快慢指针优雅地找到链表的中间结点?——LeetCode 876 题详解
🔥个人主页:@月夜的风吹雨
🎥作者简介: C++研发方向学习者
📖学习专栏:《C语言》《VS2022编译器的使用》
🌄人生格言: 任何一个伟大的思想,都有一个微不足道的开始。
文章目录
- 🔍 一、题目解析:我们到底要做什么?
- ✅ 题目描述
- ✅ 示例说明
- 示例 1:
- 示例 2:
- 🧠 二、暴力解法:先遍历一遍求长度
- 🚀 三、核心技巧:快慢指针法(Two Pointers)
- 🧪 五、测试验证
- 📊 六、性能对比
- 🔄 七、拓展思考:你能想到其他变种吗?
- 🎯 八、这篇文章能帮你什么?
- 📣 九、欢迎你参与讨论!
- 🧩 十、总结:记住这三点就够了!
🎯 掌握这一个经典技巧,轻松应对链表相关面试题!
如果你正在准备算法面试,或者想提升自己对数据结构的理解,那么链表绝对是绕不开的话题。今天我们要讲的是 LeetCode 上一道非常经典的题目:
876. 链表的中间结点(Middle of the Linked List)
别看它标着“简单”,但它的解法却蕴含着一种巧妙的思想——快慢指针(Two Pointers),这种思想在很多算法问题中都有广泛应用。
本文将带你从零开始理解这道题,并深入剖析其背后的逻辑与优化思路。读完这篇文章,你不仅能写出正确的代码,还能理解为什么这个方法是“最优”的。
🔍 一、题目解析:我们到底要做什么?
✅ 题目描述
给你单链表的头结点 head
,请你找出并返回链表的中间结点。
如果链表有两个中间结点,则返回第二个中间结点。
✅ 示例说明
示例 1:
输入: [1,2,3,4,5]
输出: [3,4,5]
解释:链表只有一个中间结点,值为 3。
示例 2:
输入: [1,2,3,4,5,6]
输出: [4,5,6]
解释:链表有两个中间结点(3 和 4),返回第二个(即 4)。
"💡 注意:这里返回的是结点本身,不是数值! "
🧠 二、暴力解法:先遍历一遍求长度
最直观的想法是:
- 先遍历一次链表,计算出总长度
n
; - 再从头开始走
n / 2 + 1
步,到达中间结点。
/*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/
struct ListNode* middleNode(struct ListNode* head) {struct ListNode* ptail = head;int count = 0; //记录节点总数while (ptail){ptail = ptail->next;count++;}count = count / 2 + 1; //找到第几个节点while (count > 1){head = head->next;count--;}return head;}
⚠️ 缺点分析
- 时间复杂度:O(n) + O(n/2) = O(n)
- 空间复杂度:O(1)
- 虽然效率尚可,但需要两次遍历,不够优雅。
“❌ 我们能不能只遍历一次就解决问题?”
🚀 三、核心技巧:快慢指针法(Two Pointers)
✅ 核心思想
让两个指针同时从头结点出发:
- 快指针(fast) 每次走两步;
- 慢指针(slow) 每次走一步。
当快指针走到末尾时,慢指针正好位于中间位置!
"🌟 这就是所谓的“龟兔赛跑”策略! "
🔁 为什么这样能行?
假设链表有 n
个结点:
- 快指针每次走 2 步,慢指针每次走 1 步;
- 当快指针走了 k 步时,慢指针走了 k/2 步;
- 当快指针走到最后一个结点或 null 时,慢指针刚好走到第 (n+1)/2 个结点,也就是第二个中间结点。
⚠️当然这个还有一个坑,就是节点总数为奇数
或偶数
时又不一样
我们可以看出节点数为奇数时,
slow
走到中间节点时fast
刚好为最后一个节点
,而为偶数时,slow
走到中间节点时fast
却指向了NULL
;
✅ 完美满足题目要求!
✅ 四、代码实现(C语言)
struct ListNode* middleNode(struct ListNode* head) {struct ListNode* fast = head;struct ListNode* slow = head;while (fast && fast->next) { //这里的fast和fast->next不能互换位置!!!slow = slow->next;fast = fast->next->next;}return slow;
}
📌 关键点解读
行号 | 说明 |
---|---|
fast && fast->next | 判断是否可以继续前进两步,避免空指针访问 |
fast = fast->next->next | 快指针跳两步 |
slow = slow->next | 慢指针走一步 |
返回 slow | 当快指针结束时,慢指针恰好在中间 |
🧪 五、测试验证
以示例 1 为例:[1,2,3,4,5]
步骤 | FAST | SLOW |
---|---|---|
初始 | 1 | 1 |
第1轮 | 3 | 2 |
第2轮 | 5 | 3 |
第3轮 | null | 3 |
✅ 结果正确:返回结点 3。
再看示例 2:[1,2,3,4,5,6]
步骤 | FAST | SLOW |
---|---|---|
初始 | 1 | 1 |
第1轮 | 3 | 2 |
第2轮 | 5 | 3 |
第3轮 | 7(null) |
✅ 返回结点 4,符合预期。
📊 六、性能对比
方法 | 时间复杂度 | 空间复杂度 | 是否需要两次遍历 |
---|---|---|---|
暴力法 | O(n) | O(1) | 是 |
快慢指针 | O(n) | O(1) | 否 |
虽然时间复杂度相同,但快慢指针只需一次遍历,更高效、更简洁,是真正的“优雅解法”。
🔄 七、拓展思考:你能想到其他变种吗?
🤔 变种 1:找倒数第 k 个结点
可以用类似双指针的方法:先让快指针走 k 步,然后一起走,直到快指针到末尾。
🤔 变种 2:判断链表是否有环
同样是快慢指针的经典应用!快指针追上慢指针则说明有环。
👉 推荐阅读:LeetCode 141. 环形链表 —— 使用快慢指针检测环的存在。
🤔 变种 3:删除链表的中间结点
可以在找到中间结点后,调整前驱节点的指针来删除。
🎯 八、这篇文章能帮你什么?
- 学会一个实用的算法技巧:快慢指针不仅适用于本题,还广泛用于环检测、查找中位数等场景。
- 提升代码质量:通过对比不同解法,理解“优雅”代码的设计思维。
- 备战面试:链表类问题是大厂常考题,掌握这类模式能让你在面试中脱颖而出。
- 建立系统性思维:从暴力解法 → 优化思路 → 拓展应用,培养完整的解题流程。
📣 九、欢迎你参与讨论!
如果你觉得这篇讲解对你有帮助,请不要吝啬你的支持👇:
✅ 点赞:让更多人看到这篇优质内容
💬 评论区留言:分享你在做这道题时遇到的坑,或者你有没有别的解法?
🔔 关注我:获取更多算法、数据结构、面试技巧干货!
"💌 你的每一次互动,都是对我最大的鼓励! "
🧩 十、总结:记住这三点就够了!
- 快慢指针是解决链表问题的强大工具,尤其适合处理“距离”、“中点”、“环”等问题。
- 不要只追求答案,更要理解背后的思想。比如为什么快指针走两步就能保证慢指针在中间?
- 多练习、多思考、多总结。把每道题都当作一次思维训练的机会。
✅ 最后说一句
“编程的本质不是写代码,而是思考。”
—— 一位资深工程师的忠告
当你面对一道看似简单的题目时,不妨问自己:“有没有更聪明的做法?”也许,下一个突破点就在你的一次深思熟虑之中。
📌 下一期预告:我们将深入讲解「反转链表」的三种解法(迭代、递归、双指针),并探讨它们的时间空间复杂度差异。记得关注哦!
✨ 感谢阅读!
如果你喜欢这种风格的技术文章,欢迎转发、收藏、点赞,也欢迎在评论区告诉我你想看哪些话题~