一、快慢指针的原理
定义:
快指针:每次移动两步
慢指针:每次移动一步
终止条件:
当快指针到达链表末尾时停止
事件复杂度:
始终为O(n),仅需依次遍历
空间复杂度:
仅需两个指针变量
二、快慢指针的典型应用场景
1、首先制作一个链表的节点
#include <iostream>
#include <vector>struct ListNode {int val;ListNode* next;ListNode(int x) :val(x), next(nullptr){}ListNode(int x, ListNode *_next):val(x),next(_next){}
};//传入列表生成list链
ListNode* makeNodeList(std::vector<int> vec)
{ListNode* outNode = nullptr;for (int i = vec.size() - 1; i >= 0 ; i--){outNode = new ListNode(vec[i],outNode);}return outNode;}//遍历输出链表
void traverseNodeList(ListNode* node)
{while (node){std::cout << node->val << " ";node = node->next;}std::cout << std::endl;
}//回收资源
void reclaim(ListNode* node)
{if (node->next){reclaim(node->next);}delete node;
}int main()
{std::vector<int> vec{ 1,2,3,4,5 };ListNode *node = makeNodeList(vec);traverseNodeList(node);reclaim(node);return 0;
}
2、检测链表是否有环
代码如下:
bool haveLoop(ListNode* node)
{ListNode* fast = node;ListNode* slow = node;while (fast != nullptr && fast->next != nullptr){fast = fast->next->next;slow = slow->next;if (fast == slow){return true; //快指针追上了满指针,有环}}return false;
}int main()
{ListNode* node5 = new ListNode(5);ListNode* node4 = new ListNode(4, node5);ListNode* node3 = new ListNode(3, node4);ListNode* node2 = new ListNode(2, node3);ListNode* node1 = new ListNode(1, node2);node5->next = node2;if (haveLoop(node1)){std::cout << "有环" << std::endl;}else {std::cout << "没环" << std::endl;}return 0;
}
3、找到链表的中间节点
ListNode* middileNode(ListNode* node)
{ListNode *fast = node, *slow = node;while (fast && fast->next) { //如果fast的next为nullptr,则下面的next->next就找不第二个next了fast = fast->next->next;slow = slow->next;}return slow;
}int main()
{std::vector<int> vec{ 1,2,3,4,5 };ListNode *node = makeNodeList(vec);ListNode *midNode = middileNode(node);std::cout << "奇数个中间节点是" << midNode->val << std::endl;std::vector<int> vec2{ 1,2,3,4,5,6 };ListNode* node2 = makeNodeList(vec2);ListNode* midNode2 = middileNode(node2);std::cout << "偶数个中间节点是" << midNode->val << std::endl;return 0;
}
4、寻找链表的环入口
代码如下:
(因为用到了前面判断是否时环的函数,所以代码都放这里了)
#include <iostream>
#include <vector>struct ListNode {int val;ListNode* next;ListNode(int x) :val(x), next(nullptr){}ListNode(int x, ListNode *_next):val(x),next(_next){}
};//传入列表生成list链
ListNode* makeNodeList(std::vector<int> vec)
{ListNode* outNode = nullptr;for (int i = vec.size() - 1; i >= 0 ; i--){outNode = new ListNode(vec[i],outNode);}return outNode;}//遍历输出链表
void traverseNodeList(ListNode* node)
{while (node){std::cout << node->val << " ";node = node->next;}std::cout << std::endl;
}//回收资源
void reclaim(ListNode* node)
{if (node->next){reclaim(node->next);}delete node;
}bool haveLoop(ListNode* node)
{ListNode* fast = node;ListNode* slow = node;while (fast != nullptr && fast->next != nullptr){fast = fast->next->next;slow = slow->next;if (fast == slow){return true; //快指针追上了满指针,有环}}return false;
}ListNode* middileNode(ListNode* node)
{ListNode *fast = node, *slow = node;while (fast && fast->next) { //如果fast的next为nullptr,则下面的next->next就找不第二个next了fast = fast->next->next;slow = slow->next;}return slow;
}ListNode* entranceNode(ListNode* node)
{if (!haveLoop(node)){return nullptr; //如果不含有环路,返回空指针}ListNode* fast = node, * slow = node;while (fast && fast->next){fast = fast->next->next;slow = slow->next;if (fast == slow){break;}}slow = node;while (fast != slow){fast = fast->next;slow = slow->next;}return slow;
}int main()
{ListNode* node5 = new ListNode(5);ListNode* node4 = new ListNode(4, node5);ListNode* node3 = new ListNode(3, node4);ListNode* node2 = new ListNode(2, node3);ListNode* node1 = new ListNode(1, node2);node5->next = node3;ListNode* node = entranceNode(node1); //Node节点std::cout << "入口节点值为" << node->val << std::endl;return 0;
}
5、判断回文链表
代码如下:
#include <iostream>
#include <vector>struct ListNode {int val;ListNode* next;ListNode(int x) :val(x), next(nullptr){}ListNode(int x, ListNode *_next):val(x),next(_next){}
};//传入列表生成list链
ListNode* makeNodeList(std::vector<int> vec)
{ListNode* outNode = nullptr;for (int i = vec.size() - 1; i >= 0 ; i--){outNode = new ListNode(vec[i],outNode);}return outNode;}//遍历输出链表
void traverseNodeList(ListNode* node)
{while (node){std::cout << node->val << " ";node = node->next;}std::cout << std::endl;
}//回收资源
void reclaim(ListNode* node)
{if (node->next){reclaim(node->next);}delete node;
}bool haveLoop(ListNode* node)
{ListNode* fast = node;ListNode* slow = node;while (fast != nullptr && fast->next != nullptr){fast = fast->next->next;slow = slow->next;if (fast == slow){return true; //快指针追上了满指针,有环}}return false;
}ListNode* middileNode(ListNode* node)
{ListNode *fast = node, *slow = node;while (fast && fast->next) { //如果fast的next为nullptr,则下面的next->next就找不第二个next了fast = fast->next->next;slow = slow->next;}return slow;
}ListNode* entranceNode(ListNode* node)
{if (!haveLoop(node)){return nullptr; //如果不含有环路,返回空指针}ListNode* fast = node, * slow = node;while (fast && fast->next){fast = fast->next->next;slow = slow->next;if (fast == slow){break;}}slow = node;while (fast != slow){fast = fast->next;slow = slow->next;}return slow;
}bool isSymmeNode(ListNode* node)
{//用前面的函数找到中间节点ListNode* midNode = middileNode(node);//反转中间节点后面的节点// 321 -> 123// 3 2 1// 3 21// 2 3 1// 1 2 3ListNode* temp;ListNode* Lnode = midNode;ListNode* Rnode = nullptr;while (Lnode){temp = Lnode->next;Lnode->next = Rnode; //让先放进来的值朝着后面排Rnode = Lnode;Lnode = temp;}//第三步、比较前后的值是否相同while(Rnode){if (node->val != Rnode->val){return false;}else {node = node->next;Rnode = Rnode->next;}}return true;
}int main()
{std::vector<int> vec = { 1,2,3,3,2,1 };ListNode* Node = makeNodeList(vec);if (isSymmeNode(Node)){std::cout << "是回文链" << std::endl;}else {std::cout << "不是回文链" << std::endl;}return 0;
}
6、找到链表的倒数第K个节点
ListNode* getKthFromEnd(ListNode* node, int k)
{ListNode* fast = node, * slow = node;for (int i = 0; i < k; i++){if (fast && fast->next){fast = fast->next;}else {return nullptr;}}while (fast){fast = fast->next;slow = slow->next;}return slow;
}int main()
{std::vector<int> vec = { 1,2,3,4,5,6 };ListNode* Node = makeNodeList(vec);std::cout << "倒数第3节点的值是" << getKthFromEnd(Node, 3)->val << std::endl;return 0;
}
三、快慢指针在实际开发中的应用
下面是DeepseeK给出的,个人认为是从判断环,找中点,业务需求速率不同这三个角度演化。还没实践过。后面有实践应该会贴链接在这里。
PS:夜已深,学生时期听的歌真有品味啊,当世界年轻时
1. 资源管理与循环检测
-
场景:在内存管理、文件系统或网络协议中,需要检测资源引用是否形成循环依赖。
-
示例:
-
垃圾回收(GC):某些垃圾回收算法需要检测对象之间的循环引用(如标记-清除算法)。通过快慢指针可以高效判断对象引用链中是否存在环,避免内存泄漏。
-
文件系统的符号链接:检测符号链接是否形成环(如
ln -s file1 file2; ln -s file2 file1
),避免程序陷入死循环遍历。
-
-
2. 数据结构的维护与优化
-
场景:在自定义链表结构或缓存系统中,需要高效操作链表。
-
示例:
-
缓存淘汰策略:在实现类似 LRU(最近最少使用)缓存时,若需手动管理链表(而非使用现成库),快慢指针可用于快速定位中间节点或批量调整缓存顺序。
-
链表合并与分割:在分布式系统中合并多个有序链表(如日志合并),通过快慢指针找到中点后分治处理(类似归并排序)。
-
-
3. 网络与协议处理
-
场景:处理网络数据包的顺序或超时检测。
-
示例:
-
心跳检测:在长连接中,若用链表维护心跳包的时间戳,快指针(高频检测)和慢指针(低频统计)可协同判断连接健康状态。
-
数据包乱序重组:通过快慢指针分离已确认和待确认的数据包链表。
-
-
4. 并发与多线程编程
-
场景:在无锁数据结构或任务调度中,检测潜在的死锁或竞态条件。
-
示例:
-
任务队列管理:在生产者-消费者模型中,快指针快速推进任务处理,慢指针统计未处理任务量,动态调整队列负载。
-
死锁检测:若线程等待关系形成链表式依赖,快慢指针可辅助检测循环等待(需结合线程调度器的元数据)。
-
-
5. 系统性能分析与调试
-
场景:在性能分析工具或调试器中,追踪程序执行路径。
-
示例:
-
调用链分析:在分布式追踪系统(如 OpenTelemetry)中,若调用链因异常形成环(如 A→B→A),快慢指针可快速定位问题节点。
-
栈溢出检测:通过快慢指针监控递归调用的深度(慢指针记录常规检查点,快指针快速探测栈增长)。
-
-
6. 游戏与图形处理
-
场景:处理动态更新链表结构的数据(如动画帧、物理引擎中的刚体链表)。
-
示例:
-
动画帧同步:快指针快速遍历待渲染帧,慢指针标记已确认同步的帧节点,避免丢帧或重复渲染。
-
碰撞检测优化:将物体按空间分组成链表,快慢指针协助快速筛选待检测的物体集合。
-
-
7. 数据库与存储引擎
-
场景:在存储引擎的索引结构(如跳表、B+树叶子节点链表)中优化查询。
-
示例:
-
跳表(Skip List)维护:快指针快速跨越高层索引,慢指针在底层链表同步移动,平衡查询与更新效率。
-
范围查询优化:通过快慢指针快速定位查询范围的起点和终点。
-
-