23 链表中环的入口节点(第3章 高质量的代码-代码的鲁棒性)

题目描述:

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出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;
    }
};

  

  

原文地址:https://www.cnblogs.com/GuoXinxin/p/10449501.html