链表中环的入口结点(剑指offer-22)

原题链接

题目描述

给定一个链表,返回链表开始入环的第一个节点。 从链表的头节点开始沿着 next 指针进入环的第一个节点为环的入口节点。如果链表无环,则返回 null。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

说明:不允许修改给定的链表。

示例1

在这里插入图片描述
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

示例2

在这里插入图片描述
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

示例3

在这里插入图片描述
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。

第1种思路

友情提示:第1种看不懂没关系,第2种更简练高效
1)使用双指针,假设环中有a个结点,那么先让第一个指针从头节点开始移动a步,然后第一个指针和第二个指针同时移动,每次移动一步,那么当两个指针相遇的时候恰好在入口结点。
可以这样理解,第一个指针先移动了a步,就相当于把头节点当做入口结点,已经完整转了一圈了,那么现在停留在入口结点(真正的头节点),然后把真正的入口结点当作头节点,那么第二个指针再从头节点开始移动到入口结点就和第一个指针的移动保持一致了。
那么现在的问题是,如何找到环中的结点数量a?
2)设一个计数器cnt = 0, 假设我们知道环中某个结点node,那么每次指针指向下一个结点,cnt++,那么当指针再次指向node结点时,就说明已经转了一圈了,此时cnt就是环中的结点数量
那么问题又变成了,如果找到环中的某个结点?
3)我们可以使用双指针中的快慢指针,慢指针每次移动一下,快指针每次移动两下,那么两指针相遇时一定在环内。可以这样理解,有一个长跑道,长跑道的尽头有一个环形跑道,那么两个人同时从长跑道开始出发,肯定是跑的快的人先进去环形跑道,而且进入环形跑道之后就不会再出来,只会一直绕圈,那么等跑得慢的人进入环形跑道之后,两人一定会在其中某个位置相遇。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode in = getInNode(head);
        if(in == null){
            return null;
        }
        int cnt = 1;
        for(ListNode node = in.next; node != in; node = node.next){
            cnt++;
        }
        ListNode slow = head;
        ListNode fast = head;
        while(cnt > 0){
            fast = fast.next;
            cnt--;
        }
        while(slow != fast){
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }

    private ListNode getInNode(ListNode head){
        if(head == null || head.next == null){
            return null;
        }
        ListNode slow = head.next;
        ListNode fast = slow.next;
        while(slow != null && fast != null){
            if(slow == fast){
                return slow;
            }
            slow = slow.next;
            fast = fast.next;
            if(fast != null){
                fast = fast.next;
            }
        }
        return null;
    }
}

第2种思路

假设头节点到入口结点之间有a个结点,环内有b个结点。
同样使用快慢指针,

那么当两指针i第一次相遇时:

假设快指针走了f的长度,慢指针走了s的长度
那么相遇时快指针一定比慢指针多走了n圈(n是未知数,可以联想环形跑道跑步),即 f = s + nb
又因为快指针每次走2下,慢指针走1下,所以 f = 2s
f=2s
f=s+nb
两式联立,s = nb, f = 2nb

然后

想一下,一个指针,从头节点开始,
第一次经过入口结点时,走了a步,
第二次经过入口结点时,走了a + b步,
第三次经过入口结点时,走了a + 2b步,

第n次经过入口结点时,走了a + nb步。

此时再观察一下上面求的式子,两指针第一次相遇时慢指针已经走了nb步了,所以只要再走a步就可以回到入口结点,而我们要求的恰好就是入口结点。

但是

我们并不知道a是多少

但是!

不要忘了a是什么,a是头节点到入口结点之间的结点数呀!
所以,只要让一个指针重新回到头节点,然后另一个指针依然在他们相遇的那个结点,然后两个指针同时移动,每次都移动1步,那么移动a步之后,第一个指针到了入口结点,第二个指针也绕回到入口节点了,即两指针相遇了! 那么我们并不需要知道a是多少,结束条件就是第一个指针 == 第二个指针

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head, fast = head;
        while(true){
            if(fast == null || fast.next == null){
                return null;
            }
            slow = slow.next;
            fast = fast.next.next;
            if(slow == fast){
                break;
            }
        }
        fast = head;
        while(slow != fast){
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值