zoukankan      html  css  js  c++  java
  • 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;
        }
    };
    

      

      

  • 相关阅读:
    P5362 [SDOI2019]连续子序列 思维题
    P5360 [SDOI2019]世界地图 虚树+最小生成树
    P4565 [CTSC2018]暴力写挂 边分治+虚树
    BZOJ2870. 最长道路tree 并查集/边分治
    P4103 [HEOI2014]大工程 虚树
    P4220 [WC2018]通道 虚树+边分治
    P3261 [JLOI2015]城池攻占 可并堆
    积水问题
    23. 合并K个排序链表
    21. 合并两个有序链表
  • 原文地址:https://www.cnblogs.com/GuoXinxin/p/10449501.html
Copyright © 2011-2022 走看看