下酒菜:带环单链表如果求是否有环?

快指针一下走两步,慢指针一下走一步,最终它们会在环中相遇
    
简单的有限证明:
        首先,它们都会进入环
        假设快指针在慢指针后面一步,下一次就追上了。(可在脑海中想一想)
        后面两步,由于快指针比慢指针多走一步,一次后就在后面一步,下一次就追上了。
        后面n步,n - 1次后就在后面一步,下一次就追上了。

方法一:断链法求环入口

现在已经已经会求单链表有无环了,下面由另一个常见的问题,引出此方法:

问题:如何判断两个无环链表是否相交,如果相交,求出第一个相交的节点

1 长链表指针先走long_len -short_len的距离,然后一一进行比较
    
2 将一个链表尾部与头部相连,形成环;
一个快指针,一个慢指针,最终它们相遇则相交,未成环的指针点走到了nullptr,则不相交;

    
下面问题来了:
                    上面把链表相交问题转化成了环问题。
                    那么从环问题的角度来看,如果把环断开,就转变变成了相交问题。
                    那么求环的入口问题,可以转化为求两个单链表求交点的问题

图形展示:
    
        如图:断开后(fast = fast->next; slow->next = NULL;)
        弄直了来看,就是一个活生生的链表相交问题,环入口点即是链表相交点。
                
        极限情况:
                    
            1,fast slow都在环入口上,断开后,新链和原链,相交于最后一个节点,能测出来
            2,fast slow都在环入口的前一个节点,断开后,新链表第一个节点就在原链表上。能测出来

C++代码:    
class Solution {
public:
	ListNode* EntryNodeOfLoop(ListNode* pHead)
	{
		ListNode* fast = pHead, *slow = pHead;
		while (fast) {
			if (!fast->next)  //奇数个节点,且到了末尾
				return NULL;
			fast = fast->next->next;
			slow = slow->next;
			if (fast == slow) {  //相遇,有环
				fast = fast->next;
				slow->next = NULL;  //解链,fast是第二个链表的头节点,slow是两个链表的尾节点
				int l1 = ListLength(fast);
				int l2 = ListLength(pHead);
				if (l1 < l2) {  //fast更长
					swap(l1, l2);
					swap(pHead, fast);
				}
				for (int i = 0; i < l1 - l2; i++)  //长链表先走l1-l2长度
					fast = fast->next;
				while (fast) {  //再一起走
					if (fast == pHead)
						return fast;
					fast = fast->next;
					pHead = pHead->next;
				}
			}
		}
		return NULL;  //偶数个节点,退出上面循环
	}

	int ListLength(ListNode* head) {  //求链表长度
		int length = 0;
		while (head) {
			length++;
			head = head->next;
		}
		return length;
	}

};

方法二:数学方法求环入口

先看一张图片,等会会用上:
    
        如果fast和slow相遇时,slow已经走完一圈,fast早就把slow追上了
        所以当fast和slow相遇时,slow还没有走完链表,假设fast已经在环内循环了n(1<= n)圈。假设slow走了s步,则fast走了2s步,又由于
        fast走过的步数 = s + n*r(快指针比慢指针走的s多走n圈,r为圈大小),则有下面的等式:
        2*s = s + n  * r ;    (1)
         =>   s = n*r      (2)
        如果假设整个链表的长度是L,入口和相遇点的距离是x(如上图所示),起点到入口点的距离是a(如上图所示),则有:
        a + x = s = n * r; (3)  由(2)推出
        a + x = (n - 1) * r + r  = (n - 1) * r + (L - a)     (4) 由环的长度 = 链表总长度 - 起点到入口点的距离求出
        最终结果:
        a = (n - 1) * r + (L -a -x)   (5)  
            
        即:起点到入口的距离 = 从相遇点到环入口的距离  + (n-1)*r
        简单的说:让一根指针从开始走,一根指针从相遇点走,最终它们会在环入口点相遇,至于第二根指针在环中走的圈数,我们并不关心。

C++代码:
class Solution {
public:
	ListNode* EntryNodeOfLoop(ListNode* pHead)
	{
		ListNode* fast = pHead, *slow = pHead;
		while (fast) {
			if (!fast->next)  //奇数个节点,且到了末尾
				return NULL;
			fast = fast->next->next;
			slow = slow->next;
			if (fast == slow) {   //相遇,有环
				while (1) {  //一个从相遇点走,一个从开头走,必会相遇,相遇点则是入口点
					if (fast == pHead)
						return fast;
					fast = fast->next;
					pHead = pHead->next;
				}
			}
		}
		return NULL;  //偶数个节点,退出上面循环
	}
};

怎么样,是不是简单多了
不得不感叹,不怕牛的程序员,就怕数学好的程序员!