142. 环形链表 II

在这里插入图片描述
进阶:你是否可以不用额外空间解决此题?

方法一:哈希表

时间复杂度:O(n) 空间复杂度:O(n)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        set<ListNode*> s;
        int n=0;
        ListNode*p=head;
        while(p){
            if(s.find(p)!=s.end())
                return p;
            s.insert(p);
            p=p->next;
        }
        return NULL;
    }
};

方法二:双指针

想法
当然一个跑得快的人和一个跑得慢的人在一个圆形的赛道上赛跑,会发生什么?在某一个时刻,跑得快的人一定会从后面赶上跑得慢的人。
算法
Floyd 的算法被划分成两个不同的 阶段 。在第一阶段,找出列表中是否有环,如果没有环,可以直接返回 null 并退出。否则,用 相遇节点 来找到环的入口。
阶段 1
这里我们初始化两个指针 - 快指针和慢指针。我们每次移动慢指针一步、快指针两步,直到快指针无法继续往前移动。如果在某次移动后,快慢指针指向了同一个节点,我们就返回它。否则,我们继续,直到 while 循环终止且没有返回任何节点,这种情况说明没有成环,我们返回 null 。
环中的节点从 0 到 C-1编号,其中 C 是环的长度。非环节点从−F到−1编号,其中F 是环以外节点的数目。F次迭代以后,慢指针指向了 0 且快指针指向某个节点 h,其中 F≡h(modC) 。这是因为快指针在 F 次迭代中遍历了2F 个节点,且恰好有 F 个在环中。继续迭代 C-h次,慢指针显然指向第 C-h号节点,而快指针也会指向相同的节点。原因在于,快指针从h号节点出发遍历了 2(C-h)个节点。
h+2(C−h)
=2C−h
≡C−h(modC)
因此,如果列表是有环的,快指针和慢指针最后会同时指向同一个节点,因此被称为相遇 。
阶段 2
给定阶段 1 找到的相遇点,阶段 2 将找到环的入口。首先我们初始化额外的两个指针: ptr1 ,指向链表的头, ptr2 指向相遇点。然后,我们每次将它们往前移动一步,直到它们相遇,它们相遇的点就是环的入口,返回这个节点。
我们利用已知的条件:慢指针移动 1 步,快指针移动 2 步,来说明它们相遇在环的入口处。(下面证明中的 tortoise 表示慢指针,hare 表示快指针)
2⋅distance(tortoise)
2(F+a)
2F+2a
F
=distance(hare)
=F+a+b+a
=F+2a+b
=b
因为 F=b,指针从 h 点出发和从链表的头出发,最后会遍历相同数目的节点后在环的入口处相遇。
时间复杂度:O(n)
对有环列表,快指针和慢指针在 F+C-h次迭代以后会指向同一个节点,正如上面正确性证明所示, F+C−h≤F+C=n ,所以阶段 1 运行时间在O(n) 时间以内,阶段 2 运行F<n 次迭代,所以它运行时间也在O(n) 以内。
对于无环链表,快指针大约需要迭代 2n次会抵达链表的尾部,这样不会进入阶段 2 就直接退出。因此,不管是哪一类链表,都会在与节点数成线性关系的时间内运行完。
空间复杂度:O(1)
Floyd 的快慢指针算法仅需要几个指针,所以只需常数级别的额外空间。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* slow=head;
        ListNode* fast=head;
        ListNode* p1=head,*p2=NULL;
        while(fast!=NULL && fast->next!=NULL){  //判断是否有环
            slow=slow->next;
            fast=fast->next->next;
            if(slow==fast){ //如果有环,将相遇点记为p2
                p2=slow;
                break;
            }
        }
        if(p2){ //当有环时,从头指针p1和相遇点p2同时向前移动,会在出口相遇
            while(p1!=p2){
                p1=p1->next;
                p2=p2->next;
            }
            return p1;
        }
        return NULL;    //没有环返回NULL
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值