剑指offer 学习笔记 链表中环的入口节点
面试题23:链表中环的入口节点。如果一个链表中包含环,如何找出环的入口节点?
解决这个问题的第一步是如何确定一个链表中存在环。我们可以定义两个指针,初始化为指向头节点的指针。两个指针同时从链表头部出发,一个指针一次走一步,另一个指针一次走两步,如果走的快的指针追上了走得慢的指针,那么链表就包含环,如果走得快的指针走到了链表的末尾都没有追上走得慢的指针,那么链表就不包含环。
第二步是找到环的入口,我们还是先定义两个指针P1、P2,都初始化为指向头节点的指针。如果链表中的环有n个节点,则指针P1先在链表上向前移动n步,然后两个指针以相同的速度向前移动,当P2指向环的入口节点时,P1已经绕了环一圈又回到了入口节点。
剩下的问题是如何得到环中节点的数目。我们之前分析到一快一慢指针相遇时,一定是在环中,可以此时从这个节点出发,一边向前移动一边计数,当再回到这个节点时,就可以得到环中节点数了:
#include <iostream>
using namespace std;
struct ListNode {
int m_nValue;
ListNode* m_pNext;
};
void AddToTail(ListNode** pHead, int value) { // 尾插法
ListNode* pNew = new ListNode(); // 为新结点分配内存
pNew->m_nValue = value;
pNew->m_pNext = nullptr;
if (*pHead == nullptr) { // 如果是空链表
*pHead = pNew;
} else {
ListNode* pNode = *pHead;
while (pNode->m_pNext != nullptr) { // 找到链表末尾
pNode = pNode->m_pNext;
}
pNode->m_pNext = pNew; // 将链表末尾元素的指针指向新节点
}
}
ListNode* MeetingNode(ListNode* pHead) { // 判断链表是否有环,如有返回环内一节点,如没有返回nullptr
if (pHead == nullptr || pHead->m_pNext == nullptr) { // 有环时链表长度大于1
return nullptr;
}
ListNode* pSlow = pHead, *pFast = pHead->m_pNext;
while (pFast->m_pNext != nullptr) {
pSlow = pSlow->m_pNext;
pFast = pFast->m_pNext;
if (pFast->m_pNext != nullptr) {
pFast = pFast->m_pNext;
}
if (pSlow == pFast) {
return pSlow;
}
}
return nullptr;
}
int NumOfNodesInLoop(ListNode* pNode) { // 环中节点数
if (pNode == nullptr || pNode->m_pNext == nullptr) { // 防止输入空指针或只有一个节点,输入一个节点时以下访问第二个节点的代码就会出错
return -1;
}
ListNode* pNodeMove = pNode->m_pNext; // 不能将pNodeMove初始化为pNode同时count初始化为0
int count = 1; // 因为下面的循环结束条件是pNode与pNodeMove相等
while (pNode != pNodeMove) {
if (pNodeMove->m_pNext != nullptr) {
pNodeMove = pNodeMove->m_pNext;
} else {
return -1;
}
++count;
}
return count;
}
ListNode* EntryNodeOfLoop(ListNode* pHead, int loopLength) { // 找环的入口节点
if (pHead == nullptr || loopLength < 2) {
return nullptr;
}
ListNode* pAhead = pHead, *pBehind = pHead;
for (int i = 0; i < loopLength; ++i) {
pAhead = pAhead->m_pNext;
}
while (pAhead != pBehind) {
pAhead = pAhead->m_pNext;
pBehind = pBehind->m_pNext;
}
return pAhead;
}
int main() {
ListNode* pHead1 = new ListNode();
pHead1->m_nValue = 0;
pHead1->m_pNext = nullptr;
AddToTail(&pHead1, 1);
pHead1->m_pNext->m_pNext = pHead1;
ListNode* pResMeetingNode = MeetingNode(pHead1);
if (pResMeetingNode) {
cout << "链表中有环。" << endl;
int numOfNodesInLoop = NumOfNodesInLoop(pResMeetingNode);
cout << "环中的节点数为:" << numOfNodesInLoop << endl;
cout << "环的入口节点值为:" << EntryNodeOfLoop(pHead1, numOfNodesInLoop)->m_nValue << endl;
} else {
cout << "链表中没有环。" << endl;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)