【剑指offer】链表中环的入口

🔥题目

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回null。

3 → 1 → 2 → 4 → 0 → 2
			↑       ↓
			5 ← 1 ← 6

 

☘️解析

既然谈到了链表中的环问题,那么咱们就不局限在这一题了。下面将完整的阐述「Floyd判环算法」。

1)判定链表是否有环

slow和fast从链表头出发,slow走一步,fast走两步,若相遇(slow==fast)则存在环。

这很好理解。比如两个人一快一慢,在操场跑步,如果操场是环形的(废话),那么二者一定会相遇。
所以,slow走一步,fast走三步也是可以的。

2)找环的入口

slow放回链表头重新开始,fast从其相遇的地方开始,slow走一步,fast走一步,相遇点为环的入点。

这里不好理解,可以画画图,紧跟着下面的逻辑多看几遍。开始吧!

设直线部分长度为m,环部分长度为n,快慢指针在距离入口k处相遇。

fast走的路程:m + An + k = 2s
slow走的路程:m + Bn + k = s

二者相减,得到:(A - B)n = s
又有:s = m + Bn + k
结论是,s是圈长度n的整倍数,又因为s = m + Bn + k,Bn本身就是n的整倍数,所以m + k是圈长度n的整倍数

接着,slow被拿回起点,fast留在相遇点。
以入点为视角中心,slow距离入点-m,fast距离入点k。经过m时间后,slow位于入点,fast距离入点k+m——而k+m是圈长度n的整倍数,所以此时slow和fast都位于入点!

3)求环的大小

slow不动,fast前进,再次相遇时走的fast刚刚走的步数为环的大小

这一部分没什么技术含量,从入点出发,再回到入点,走的步数自然就是环的大小。

 

🧊代码

1)判定是否存在环

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (true) {
            if (fast == null || fast.next == null) {
                return false;
            }
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {
                break;
            }
        }
        return true;
    }
}

2)找环的入口

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        // slow和fast从链表头出发,slow走一步,fast走两步,若相遇(slow==fast)则存在环
        while (true) {
            if (fast == null || fast.next == null) {
                return null;
            }
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {
                break;
            }
        }
        // slow放回链表头重新开始,fast从其相遇的地方开始,slow走一步,fast走一步,相遇点为环的入点
        slow = head;
        while (slow != fast) {
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
}

3)求环的长度

public class Solution {
    public ListNode lenOfCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        // slow和fast从链表头出发,slow走一步,fast走两步,若相遇(slow==fast)则存在环
        while (true) {
            if (fast == null || fast.next == null) {
                return null;
            }
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {
                break;
            }
        }
        // slow放回链表头重新开始,fast从其相遇的地方开始,slow走一步,fast走一步,相遇点为环的入点
        slow = head;
        while (slow != fast) {
            slow = slow.next;
            fast = fast.next;
        }
        // slow不动,fast前进,再次相遇时走的fast刚刚走的步数为环的大小
        int len = 0;
        while (true) {
            slow = slow.next;
            fast = fast.next.next;
            len++;
            if (slow == fast) {
                break;
            }
        }
        return len;
    }
}

 

🌸补充

Floyd判环算法三步走:

1)slow和fast从链表头出发,slow走一步,fast走两步,若相遇(slow==fast)则存在环。(fast走三步也行)

2)slow放回链表头重新开始,fast从其相遇的地方开始,slow走一步,fast走一步,相遇点为环的入点。(哪个放回表头都可以)

3)slow不动,fast前进,再次相遇时走的fast刚刚走的步数为环的大小。(哪个不动哪个走都可以)


最后,你可能会对下面的写法感到奇怪:

// 正确的写法
while (true) {
	if (fast == null || fast.next == null) {
		return null;
	}
	slow = slow.next;
	fast = fast.next.next;
	if (slow == fast) {
		break;
	}
}

这是因为,slow和fast开始时就是相同的,如果像下面那样写,while一次都不会执行:

// 错误的写法
while (slow != fast) {
	if (fast == null || fast.next == null) {
		return null;
	}
	slow = slow.next;
	fast = fast.next.next;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值