题目描述:
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
测试用例:
功能测试(环入口在头节点、中间节点、尾节点)
特殊输入测试(链表为空、链表没有环)
解题思路:
1)··· 确定链表中是否存在环:定义两个指针,同时从链表头节点出发,一个指针一次走一步,一个指针一次走两步。如果走的快的指针追上了走的慢的指针,说明链表中包含环;如果走的快的指针走到了链表的末尾(next指向Null)都没有追上第一个指针,那么链表就不包含环。
··· 确定环中的节点个数:从相遇的节点出发,一边继续向前一边计数,再次回到这个节点时,则遍历完一遍环中的节点。
··· 巡展环中的入口节点:使用两个指针,一个指针从头节点先向前移动环中节点的个数(该指针到达环中的入口节点还差 环前面的非环节点数),设置另一个指针,初始化在头节点(该节点据环还差 环前面的非环节点数),因此让两个指针同时向前以相同速度向前移动。两者会在入口节点相遇。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
ListNode* pMeetingNode = MeetingNode( pHead);
if(pMeetingNode == nullptr) //没有相遇,说明没有环
return nullptr;
//计算环中的节点个数:
int numCircle = 1; //也可以定义为名字:nodesInLoop
ListNode* searchNode = pMeetingNode->next;
while(searchNode != pMeetingNode){
++numCircle;
searchNode = searchNode->next;
}
//先将一个节点挪动numCircle次
ListNode* moveToTail = pHead;
for(int i=0; i<numCircle; ++i){
moveToTail = moveToTail->next;
}
//两个节点同时移动,相遇处,即环节点的入口
searchNode = pHead;
while(searchNode != moveToTail){
searchNode = searchNode->next;
moveToTail = moveToTail->next;
}
return searchNode;
}
//该函数需要重点理解,极容易忘记判断。
ListNode* MeetingNode(ListNode* pHead){
if(pHead == nullptr)
return nullptr;
ListNode* OneStep = pHead->next; //每次走一步
if(OneStep==nullptr) //只有一个节点且没有环
return nullptr;
ListNode* TwoStep = OneStep->next; //每次走两步
//是否可以追上,是有环存在,否没有环存在
while(OneStep!= nullptr && TwoStep!= nullptr){
if(OneStep==TwoStep)
return TwoStep;
OneStep = OneStep->next; //走一步
TwoStep = TwoStep->next; //走一步,还应该在走一步,但一定要判断是否可以继续走
if(TwoStep!= nullptr) //等于:不会进入下一个循环
TwoStep = TwoStep->next;
}
return nullptr;
}
};
2)同方法2思路一直,但是不需要重新寻找据头节点的第n个节点
推导过程:
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
ListNode* pMeetingNode = MeetingNode( pHead);
if(pMeetingNode == nullptr) //没有相遇,说明没有环
return nullptr;
//计算环中的节点个数:
int numCircle = 1; //也可以定义为名字:nodesInLoop
ListNode* searchNode = pMeetingNode->next;
while(searchNode != pMeetingNode){
++numCircle;
searchNode = searchNode->next;
}
//先将一个节点挪动numCircle次,不用挪动,因为pMeetingNode就在要挪动的位置出!!!
/*ListNode* moveToTail = pHead;
for(int i=0; i<numCircle; ++i){
moveToTail = moveToTail->next;
}*/
//两个节点同时移动,相遇处,即环节点的入口
searchNode = pHead;
while(searchNode != pMeetingNode){
searchNode = searchNode->next;
pMeetingNode = pMeetingNode->next;
}
return searchNode;
}
//该函数需要重点理解,极容易忘记判断。
ListNode* MeetingNode(ListNode* pHead){
if(pHead == nullptr)
return nullptr;
ListNode* OneStep = pHead->next; //每次走一步
if(OneStep==nullptr) //只有一个节点且没有环
return nullptr;
ListNode* TwoStep = OneStep->next; //每次走两步
//是否可以追上,是有环存在,否没有环存在
while(OneStep!= nullptr && TwoStep!= nullptr){
if(OneStep==TwoStep)
return TwoStep;
OneStep = OneStep->next; //走一步
TwoStep = TwoStep->next; //走一步,还应该在走一步,但一定要判断是否可以继续走
if(TwoStep!= nullptr) //等于:不会进入下一个循环
TwoStep = TwoStep->next;
}
return nullptr;
}
};
3)断链法:思考这种思想,但不建议使用,除非允许修改原来的链表
//实现1
//断链法
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
if(pHead==nullptr || pHead->next==nullptr)
return nullptr;
ListNode* slow = pHead;
ListNode* fast = pHead->next;
while(fast!=nullptr){
slow->next=nullptr; //断开
slow = fast;
fast = fast->next;
}
if(slow == nullptr && fast == nullptr) //没有环的时候
return nullptr;
return slow;
}
};
//实现2
//断链法
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
if (pHead == nullptr || pHead->next == nullptr) {
return nullptr;
}
ListNode* fast = pHead->next;
ListNode* slow = pHead;
ListNode* seg = new ListNode(1);
while (fast != nullptr) {
slow->next = seg;
slow = fast;
if (fast->next == seg) {
return fast;
}
fast = fast->next;
}
if (slow->next == seg) { // 作用是什么?不添加也可以通过编程
return slow;
}
return nullptr;
}
};
4)使用STL中的set,set有一个特性就是不能插入相同元素,这样只需遍历原List一次就可以判断出有没有环,还有环的入口地址。
s.insert(node).second这里在插入的同时也判断了插入是否成功,如果不成功表明set中已经有该元素了,该元素就是环的入口元素。
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
if(pHead==nullptr ||pHead==nullptr)
return nullptr;
set<ListNode*> nodesSet;
ListNode* pNode = pHead;
while(pNode!=nullptr){
if(nodesSet.insert(pNode).second) //插入节点:没有重复节点
pNode = pNode->next;
else
return pNode;
}
return nullptr;
}
};
s.insert(node).second用法说明:set的带有一个键参数的insert版本函数返回pair类型对象,该对象包含一个迭代器(第一个参数,first调用)和一个bool值(第二个参数,second调用),迭代器指向拥有该键的元素,而bool值表明是否添加了元素。
5)用map对访问过的节点做标记,这样如果访问一个节点两次,表明找到了环入口,否则没有环。 时间复杂度:O(n)
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
if(pHead==nullptr ||pHead==nullptr)
return nullptr;
map <ListNode*,int> mlist; //用map关联各个链表指针和对应的映射值
ListNode* pNode = pHead;
while(pNode!=nullptr && mlist[pNode]!=1){
mlist[pNode]=1;
pNode = pNode->next;
}
if(pNode==nullptr)
return nullptr;
return pNode;
}
};