剑指offer 56:求链表中环的入口节点
题目:一个链表中包含环,如何找出环的入口节点?
预备知识:判断链表是否有环
用一个指针
该思路比较简单,设置一个指针,依次遍历链表,每走一步判断是否等于头即可。
代码如下:
bool IsCLinkList(LinkList L) {
if (!L) {
return false;
}
LNode *p = L;
while (p && p->pNext != L) {
p = p->pNext;
}
return (p == nullptr ? false : true);
}
用两个指针
对于两个指针pSlow和pFast,最初都指向头,pSlow每次只走一步,pFast每次走两步,然后判断两者是否相等。如果是循环链表,那么两者一定会有相遇的时候,否则会退出程序。
代码如下:
bool IsCLinkList_ver2(LinkList L) {
LNode *pSlow = L;
if (!pSlow) {
return nullptr;
}
LNode *pFast = pSlow->pNext;
while (pSlow && pFast) {
if (pSlow == pFast) {
return true;
}
//pSlow先走一步
pSlow = pSlow->pNext;
//pFast后走两步
pFast = pFast->pNext;
if (pFast) {
pFast = pFast->pNext;
}
}
return false;
}
思路分析
回到该题目,思路分析:
a 假设能知道带环部分的节点个数n。那么采用两个指针,先让pFast先走n步,然后再让pSlow从头开始和pFast同时走,他们相遇的点,即为带环链表的入口点。
b 关键是如何得出带环部分的节点个数呢?
首先得找到带环部分的一个节点,然后通过设置两个指针,一个不动,另一个一直往下走,边走边计数,直到两者再次相遇。
主程序编写
根据思路分析,编写代码如下:
//找到带环链表中一个节点
LNode* q56_GetMeetingNode(LinkList L) {
if (!L) {
return nullptr;
}
LNode *pSlow = L;
LNode *pFast = pSlow->pNext;
while (pSlow && pFast) {
if (pSlow == pFast) {
return pFast;
}
pSlow = pSlow->pNext;
pFast = pFast->pNext;
if (pFast) {
pFast = pFast->pNext;
}
}
return nullptr;
}
//求带环链表入口节点
LNode* q56_GetEntrance(LinkList L) {
//找到链表带环部分任意一个节点
LNode *pMeet = q56_GetMeetingNode(L);
if (!pMeet) {
return nullptr;
}
//求出带环部分共有多少个节点
int nClistNode = 1;
LNode *p = pMeet;
while (p->pNext != pMeet) {
++nClistNode;
p = p->pNext;
}
//设置两个指针,让其中一个先走n步,然后同时走,相遇点即为环入口
LNode *pFast = L;
for (int i = 0; i < nClistNode; ++i) {
pFast = pFast->pNext;
}
LNode *pSlow = L;
while (pSlow != pFast) {
pSlow = pSlow->pNext;
pFast = pFast->pNext;
}
return pSlow;
}
测试代码
1)建立带环链表
a 先建立循环链表
void CreateCLinkListWithoutHead(LinkList &L, int nInputLength) {
if (nInputLength < 1) {
cerr << "argument error";
return ;
}
LNode *pNode = nullptr, *pRear = nullptr;
cout << "input " << nInputLength << " numbers: ";
for (int i = 1; i <= nInputLength; ++i) {
pNode = new LNode;
cin >> pNode->data;
if (nullptr == L) {
L = pNode;
pRear = pNode;
} else {
pRear->pNext = pNode;
pRear = pRear->pNext;
}
}
pRear->pNext = L;
}
void PrintCLinkListWithoutHead(LinkList L) {
if (!L) {
cerr << " CList is empty.";
return;
}
cout << " The list is: ";
LNode *p = L;
while (p->pNext != L) {
cout << p->data << " ";
p = p->pNext;
}
cout << p->data;
}
b 后建立单链表,让单链表表尾指向循环链表表头。
void CreateLinkListWithoutHead(LinkList &L, int nInputLength) {
if (nInputLength < 1) {
cerr << "argument error";
return;
}
LNode *pNode = nullptr, *pRear = nullptr;
cout << "input " << nInputLength << " numbers: ";
for (int i = 1; i <= nInputLength; ++i) {
pNode = new LNode();
cin >> pNode->data;
if (nullptr == L) {
L = pNode;
pRear = L;
} else {
pRear->pNext = pNode;
pRear = pRear->pNext;
}
}
pRear->pNext = nullptr;
}
void PrintLinkListWithoutHead(LinkList L) {
if (nullptr == L) {
cout << " List is empty.";
return ;
}
cout << " The list is: ";
LNode *p = L;
while (p) {
cout << p->data << " ";
p = p->pNext;
}
}
2)完整测试代码如下
//建立单链表
LinkList L1 = nullptr;
int nLength;
cout << "input the length: ";
cin >> nLength;
CreateLinkListWithoutHead(L1, nLength);
PrintLinkListWithoutHead(L1); cout << endl;
//建立循环链表
LinkList L = nullptr;
cout << "input the length: ";
cin >> nLength;
CreateCLinkListWithoutHead(L, nLength);
PrintCLinkListWithoutHead(L); cout << endl;
//得到单链表表尾
LNode *p = L1;
while (p->pNext) {
p = p->pNext;
}
//让单链表表尾指向循环链表表头,得到带环链表
p->pNext = L;
//测试主程序
LNode *pEntrance = q56_GetEntrance(L1);
if (pEntrance) {
cout << pEntrance->data << endl;
} else {
cout << "the list doesn't contain circle part.";
}
总结
1)题目本身的难点在于设置两个指针的技巧:
a 让pFast先走n步
b 再让pSlow从头开始和pFast同时走,相遇即是环入口。(我还想了好半天,为什么是这样。最怕的就是这种逻辑问题,不会这样去想,只有多见,多写,多积累,才能在某一天能想出解法。)
2)知道思路之后,写代码,用了大半个小时,其中写循环链表时有些逻辑错误。说明基本功还是不够扎实。