【快慢指针,双指针法】链表中环的入口结点

版权声明:本文为博主原创学习笔记,如需转载请注明来源。 https://blog.csdn.net/SHU15121856/article/details/82455280

面试题23:链表中环的入口结点

一个单向链表中包含环,如何找出环的入口结点?

包含环的链表,在进入环以后就会一直在环里循环。注意,因为单向链表是只有一个next的,所以链表的环一定是下图左边的形状,而不会像右边这样有走出去的可能:
这里写图片描述

[1]判断有没有环

因为进了环就出不来,使用快慢指针,如果走得快的指针追上了走得慢的指针,那么链表就包含环

[2]寻找入口结点

只要让两个指针相差一个环的长度。即若环的长度为n,则先让P1在链表上走n步(差出1个环的距离),然后P2从头结点开始,两个指针以相同的速度向前移动,当P2指向入口结点时,P1因为比P2领先一个环的长度,也绕回来指向了入口结点,即此时P1P2是重合的。

[3]解决环的长度问题

快慢指针时,两个指针相遇的结点一定在环中,所以只要标记此处,然后向前边走边计数,直到再回到此处即可。



总之本题就是先用快慢指针进入环内某个结点,然后跑一圈找到环长,再用双指针从头结点开始跑一下找到环起始结点。

#include<bits/stdc++.h>
#include "../Utilities/List.h"
using namespace std;

//输入链表的头结点,用快慢指针找到环内的一个结点,如果不存在环返回nullptr 
ListNode* MeetingNode(ListNode* pHead) {
    //输入非空校验 
    if(pHead == nullptr)
        return nullptr;
    //慢指针:初始在头结点后1个指针 
    ListNode* pSlow = pHead->m_pNext;
    if(pSlow == nullptr)//每次都要判断非空,确保有环 
        return nullptr;
    //快指针:初始在头结点后2个指针 
    ListNode* pFast = pSlow->m_pNext;
    //每次判断当下非空,再往下走
    //实际上根本没必要再去检查慢指针了,这里还是保留作者的写法 
    //[注意]有环的链表往后走绝不会走到空 
    while(pFast != nullptr && pSlow != nullptr) {
        if(pFast == pSlow)//如果两指针相遇 
            return pFast;//那么该处一定是环内一结点,将其返回 
        //慢指针每次走一步 
        pSlow = pSlow->m_pNext;
        //快指针每次走两步 
        pFast = pFast->m_pNext;//第一步可以直接走,因为while循环里判断过了 
        if(pFast != nullptr)//第二步就需要再判断一次再走了 
            pFast = pFast->m_pNext;
    }
    //如果while结束了还没返回,说明发现了空,链表没有环 
    return nullptr;
}

//输入链表头结点,找到链表中环的起始结点 
ListNode* EntryNodeOfLoop(ListNode* pHead) {
    //找到环内的某个结点 
    ListNode* meetingNode = MeetingNode(pHead); 
    if(meetingNode == nullptr)//如果返回了nullptr 
        return nullptr;//说明不存在环,这个函数也返回nullptr 

    //得到环中结点的数目
    int nodesInLoop = 1;//从1(刚刚找到的这个结点)开始计数 
    ListNode* pNode1 = meetingNode;//找到的结点用于标记当下位置,另找一个跑动的指针 
    //只要接下来不是标记位置,就说明没遍历完这个环 
    while(pNode1->m_pNext != meetingNode) {
        pNode1 = pNode1->m_pNext;//跑动指针向下走 
        ++nodesInLoop;//计数+1 
    }

    //至此,已经得到了环长(环中结点数),保存在nodesInLoop中 

    //双指针法,先移动pNode1为先行 
    pNode1 = pHead;//从头结点开始移动 
    for(int i = 0; i < nodesInLoop; ++i)//次数为环中结点的数目
        pNode1 = pNode1->m_pNext;

    ListNode* pNode2 = pHead;//pNode2踩上头结点
    //两个指针同时向后移动,直到相遇为止 
    while(pNode1 != pNode2) {
        pNode1 = pNode1->m_pNext;
        pNode2 = pNode2->m_pNext;
    }
    //双指针相遇的位置即为所求的环的起始结点 
    return pNode1;
}

int main() {
    //创建结点 
    ListNode* pNode1 = CreateListNode(1);
    ListNode* pNode2 = CreateListNode(2);
    ListNode* pNode3 = CreateListNode(3);
    ListNode* pNode4 = CreateListNode(4);
    ListNode* pNode5 = CreateListNode(5);
    //连成带环的链,即5绕回到3上 
    ConnectListNodes(pNode1, pNode2);
    ConnectListNodes(pNode2, pNode3);
    ConnectListNodes(pNode3, pNode4);
    ConnectListNodes(pNode4, pNode5);
    ConnectListNodes(pNode5, pNode3);
    //计算起始结点,并输出 
    PrintListNode(EntryNodeOfLoop(pNode1));
    //销毁链表 
    DestroyList(pNode1);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/SHU15121856/article/details/82455280