剑指offer 链表中环的入口节点

南笙酒味 提交于 2020-02-07 06:52:36

1.题目

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

来源:剑指offer
链接:https://www.nowcoder.com/practice/253d2c59ec3e4bc68da16833f79a38e4?tpId=13&tqId=11208&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

2.我的题解

2.1 快慢追赶法

先看看在同一个环中快追慢需要的时间(快指针速度2,慢指针速度1,追上是指二者重合),分析一下站位:

  • 假设二者位置重合,那么已经追上了;
  • 假设快指针落后于慢指针一个位置,那么1单位时刻后快指针追上慢指针;
  • 假设快指针落后于慢指针两个位置,那么2单位时刻后快指针追上慢指针;
  • 假设快指针在慢指针前面一个位置,那么len-1时刻后快指针追上慢指针,其中len是环的长度;
  • 一般地,如果快指针距离慢指针距离为n(顺环方向计算距离),那么快指针追上慢指针需要n单位时间,因为快指针每单位时间可以追上慢指针1个单位距离;
  • 显然快慢指针初始站位最大距离小于环的周长,最坏情况下为len-1;该情况下快指针追上慢指针时慢指针移动了len-1单位距离,快指针移动了2*len-2单位距离;
  • 主要想得出结论:环中快指针从任意位置开始追任意位置的慢指针,快指针总是可以追上慢指针,且快指针追上慢指针时慢指针移动的距离小于环的长度;
    在这里插入图片描述
    接下来看看在链表中(有环)追赶的情况:
  • 假设二者从头结点出发,快指针速度为2,慢指针速度为1
  • 根据上面的分析,慢指针进入环后,快指针在将来的某个时刻会追上慢指针,且被追上时慢指针移动的距离小于环的长度(重点);
  • 假设追上时的情形如下图所示,最终位置将环分成y和z两部分,那么可以得到关系式:

    x+k(y+z)=2(x+y)

    左边是快指针移动的距离,右边是慢指针移动的距离,k代表了快指针绕圈圈的圈数,它可能取任意自然数(k=0,1,2,3...)。由上面的结论可以确定慢指针一圈也没绕就被指针追上了;整理可得

    x=(k-1)(y+z)+z

    这个公式说明了,x的距离等于环长的倍数加上z的长度;即从头结点与相遇的位置以相同的速度前进,将在环的入口处相遇;
  • k=0时,可以得到x=-y;这个比较特殊,显然x,y,z都是正值,于是x=y=0;也就是说整个链表都是环,头结点(给哪个就是哪个)就是环的入口;
  • 假如没有环,可以预料到在追赶的过程中快指针会到达空节点,注意判断即可;
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
  • 推导是我自己推导的,但快慢指针的思想来自评论区的大佬们;大佬们太强了,怎么想起来的快慢指针?
    在这里插入图片描述
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if(!pHead)return NULL;
        ListNode * fast=pHead,*slow=pHead;
        do{
            if(!fast || !fast->next)return NULL;
            fast=fast->next->next; 
            slow=slow->next;
        }while(fast!=slow);
        
        fast=pHead;
        while(fast!=slow){
            fast=fast->next;
            slow=slow->next;
        }
        return fast;
    }
};

3.别人的题解

3.1 断链法

  • 访问完一个节点继续访问下一个节点,且更改当前节点指向空,直到没有下一个节点可以访问;
  • 会更改原链表;
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
  • 发现一个小问题,假如链表无环的话,该方法返回最后一个节点而不是返回空节点;但链表中有环的时候,该方法确实可以返回环的入口;
  • 虽然本题说了需要判断是否有环,但测试用例似乎全部有环,所以该方法可以AC;
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if(!pHead || !pHead->next)return NULL;
        ListNode * cur=pHead,*next=cur->next;
        while(next){
            cur->next=NULL;
            cur=next;
            next=next->next;
        }
        return cur;
    }
};

3.2 set、map容器

  • 遇到节点就加入集合,如果遇到添加节点时发现已经有了该节点,那么该节点就是环的入口;
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if(!pHead || !pHead->next)return NULL;
        ListNode * cur=pHead;
        set<ListNode *> myset;
        while(cur){
            if(myset.find(cur)!=myset.end())return cur;
            myset.insert(cur);
            cur=cur->next;
        }
        return cur;
    }
};

4.总结与反思

(1)快慢指针追赶法;

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!