(Leetcode 142)Linked List Cycle (II) (快慢指针详解)

Leetcode141题和142题很相似,都涉及到了快慢指针的算法。快慢指针就是设置两个指针,一个快指针,一个慢指针来达到解题的目的。下面分为以下几个模块来讲解:

  1. 解第141题
  2. 解第142题
  3. 为什么快指针一定要设置为慢指针的2倍
  4. 快慢指针的拓展应用

一、Leetcode 141题

题目为:
Linked List Cycle Given a linked list, determine if it has a cycle in it.

Follow up:

Can you solve it without using extra space?

解法思想: 算法思想就是设置一个快指针fp和一个慢指针sp,两个指针起始同时指向head节点,其中快指针每次走两步,慢指针每次走一步,那么如果链表有环的话他们一定能够相遇。可以想象两个人同时从操场上起跑,其中甲的速度是乙速度的2倍,那么当乙跑完一圈的时候甲也跑了两圈,他们一定能够相遇。

下面看具体的Java代码:

public class Solution {
   public boolean hasCycle(ListNode head) {
		if( head == null || head.next == null ){
			return false;
		}
		// 快指针fp和慢指针sp,
        ListNode fp = head, sp = head; 
        while( fp != null && fp.next != null){
        	sp = sp.next;
        	fp = fp.next.next;
        	if( fp == sp ){
        		return true;
        	}
        
        }
        return false;
    }
}

二、Leetcode 142题

题目为:
Linked List Cycle II Given a linked list, return the node where the cycle begins. If there is no cycle, return null.

Note: Do not modify the linked list.

Follow up:

Can you solve it without using extra space?

解法思想: 主要思想和上一个算法前面基本一样,也是设置一个快指针fp和一个慢指针sp,所不同的是这回是要求出如果链表存在环,那么求出环入口的起始结点。接下来我们就是要从上面的算法中做出一定程度的改变就可以完成题目的要求了。我首先说一下具体的解法,推导过程后面再说。 如果链表中存在环,那么fp和sp一定会相遇,当两个指针相遇的时候,我们设相遇点为c,此时fp和sp都指向了c,接下来令fp继续指向c结点,sp指向链表头结点head,此时最大的不同是fp的步数变成为每次走一步,令fp和sp同时走,每次一步,那么它们再次相遇的时候即为环的入口结点。 接下来我们也许想知道为什么fp和sp指向上面所说的结点之后同时以步数为一走再次相遇的时候一定会指向环的入口结点?

推导:
《(Leetcode 142)Linked List Cycle (II) (快慢指针详解)》

问题可以转化成求c到d的距离cd为什么等于h到d的距离hd?

首先如图所示,链表的整个长度为L,链表头head为h,假设fp和sp按照箭头所示的方向走。其中环入口点为d,h到d的距离hd为a。fp和sp假设初次相遇在c,初次相遇的时候慢指针sp肯定没有走完整个链表。设d到c的距离dc为x,h到c的距离即为sp初次相遇所走的路程,即设hc长度为s。此外设环的长度为r。而在fp和sp初次相遇在c点的时候,fp则在环内已经走了n圈。由于fp的速度是sp的2倍,接下来我们可以得出:

2s = s + nr

->    s = nr  (1)

又因为hd距离为a,dc距离为x,hc距离为s,所以可以得出:

 a + x = s   (2)

结合(1)和(2)可以得出:

a + x = nr   ->    a + x = (n-1)r + r   ->    a + x = (n-1)r + (L-a)        注释:L-a即为环长r

->     a = (n-1)r + (L-a-x)

即此时h到d的距离a等于c到d的距离(L-a-x)。所以当fp和sp初次相遇在c点的时候,令fp从c点出发,sp指向

链表头h,两个同时以步数为1同时出发,则再次相遇的时候即为环的入口节点d。

接下来看具体的java代码:

public class Solution {
    public ListNode detectCycle( ListNode head ) {
		if( head == null || head.next == null ){
			return null;
		}
		// 快指针fp和慢指针sp,
        ListNode fp = head, sp = head;
        while( fp != null && fp.next != null){
        	sp = sp.next;
        	fp = fp.next.next;
        	//此处应该用fp == sp ,而不能用fp.equals(sp) 因为链表为1 2 的时候容易
        	//抛出异常
        	if( fp == sp ){  //说明有环
        		break;
        	}
        }
        //System.out.println( fp.val + "   "+ sp.val );
        if( fp == null || fp.next == null ){
        	return null;
        }
        //说明有环,求环的起始节点
        sp = head;
        while( fp != sp ){
        	sp = sp.next;
        	fp = fp.next;
        }
        return sp;
    }
}

三、为什么快指针一定要设置为慢指针的2倍

看了上面的题解也许你会问问什么快指针一定要设置为慢指针的2倍呢,为什么不是3倍。例如两个人同时从操场出发,其中甲的速度是乙的三倍,当乙跑完一圈的时候,甲跑了三圈,不也是可以相遇的吗。 接下来我会推导,设快指针fp的速度是慢指针速度sp的t倍,其他参数还和上面图片即解释一致。我们可以得出: ts = s + nr 

->   (t-1)s = nr (3)

由(2)和(3)可以得到:

(t-1)(a+x) = nr

->  (t-1)(a+x) = (n-1)r + r

->  (t-1)(a+x) = (n-1)r + (L-a)

->  a = (n-1)/(t-1)*r + [(L-a)/(t-1) – x]

其中a必定为整数,因为a代表的是h到d的结点个数。所以(n-1)/(t-1)和(L-a)/(t-1) 也须为整数,所以当t为2的时候,一定为整数,当然t也可以为其他,只是遇到链表不一样的情况时候所得到的a不一定为整数。

四、快慢指针的拓展应用

目前我遇到的主要有以下几点应用:

  • 如上面第一题,判断一个链表是否有环
  • 如上面第二题,求一个链表是否存在环,如果存在,则求出环的入口结点
  • 另一个应用是求链表是否存在环的变式,如给定两个链表A和B,判断两个链表是否相交,解决方法就是将A链表尾节点指向头结点形成一个环,检测B链表是否存在环,如果存在,则两个链表相交,而检测出来的依赖环入口即为相交的第一个点。
  • 求有序链表中求出其中位数,这种问题也是设置快慢指针,当快指针到底链表尾部的时候,慢指针刚好指向链表中间的结点。

以上就是Leetcode两道题的解法和快慢指针的详细解释。如果有什么问题可以指出来一起探讨,当然如果觉得不错,可以顶一下,谢谢!

———EOF————

参考文献: https://yq.aliyun.com/articles/27081

    原文作者:约瑟夫环问题
    原文地址: https://blog.csdn.net/willduan1/article/details/50938210
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞