题目:如何判断单链表里面是否有环?
方法一:快慢指针法
设两个工作指针,一个快一个慢,如果有环的话,它们会必然在某点相遇。
为什么当单链表存在环时,p和q一定会相遇呢?
假定单链表的长度为n,并且该单链表是环状的,那么第i次迭代时,p指向元素i mod n,q指向2i mod n。因此当i≡2i(mod n)时,p与q相遇。而i≡2i(mod n) => (2i - i) mod n = 0 => i mod n = 0 => 当i=n时,p与q相遇。这里一个简单的理解是,p和q同时在操场跑步,其中q的速度是p的两倍,当他们两个同时出发时,p跑一圈到达起点,而q此时也刚好跑完两圈到达起点。
那么当p与q起点不同呢?假定第i次迭代时p指向元素i mod n,q指向k+2i mod n,其中0<k<n。那么i≡(2i+k)(mod n) => (i+k) mod n = 0 => 当i=n-k时,p与q相遇。
扩展:
1. 如果两个指针的速度不一样,比如p,q,( 0<p<q)二者满足什么样的关系,可以使得两者肯定交与一个节点?
Sp(i) = pi
Sq(i) = k + qi
如果两个要相交于一个节点,则 Sp(i) = Sq(i) => (pi) mod n = ( k+ qi ) mod n =>[ (q -p)i + k ] mod n =0
=> (q-p)i + k = Nn [N 为自然数]
=> i = (Nn -k) /(p-q)
i取自然数,则当 p,q满足上面等式 即 存在一个自然数N,可以满足Nn -k 是 p - q 的倍数时,保证两者相交。
特例:如果q 是p 的步长的两倍,都从同一个起点开始,即 q = 2p , k =0, 那么等式变为: Nn=i: 即可以理解为,当第i次迭代时,i是圈的整数倍时,两者都可以交,交点就是为起点。
2.如何判断单链表的环的长度?
记录下问题1的碰撞点p,slow、fast从该点开始,再次碰撞所走过的操作数就是环的长度s。
3. 如何找到链表中第一个在环里的节点?
假设链表长度是L,前半部分长度为k-1,那么第一个再环里的节点是k,环的长度是 n, 那么当q=2p时, 什么时候第一次相交呢?当q指针走到第k个节点时,q指针已经在环的第 k mod n 的位置。即p和q 相差k个元素,从不同的起点开始,则相交的位置为 n-k, 则有了下面的图:
从图上可以明显看到,当p从交点的位置(n-k) ,向前遍历k个节点就到到达环的第一个几点,节点k.
算法就很简单: 一个指针从p和q 中的第一次相交的位置起(n-k),另外一个指针从链表头开始遍历,其交点就是链表中第一个在环里的交点。
也有大神说这里其实是一个定理:碰撞点p到连接点的距离=头指针到连接点的距离,因此,分别从碰撞点、头指针开始走,相遇的那个点就是连接点。
实际上和我们上面分析的是一个意思,要理解着来还是背下来就随便你咯,定理证明见:http://blog.sina.com.cn/s/blog_725dd1010100tqwp.html
4、带环链表的长度是多少?
问题3中已经求出连接点距离头指针的长度,加上问题2中求出的环的长度,二者之和就是带环单链表的长度
4. 如果判断两个单链表有交?第一个交点在哪里?
1 bool IsExitsLoop(slist *head) 2 { 3 slist *slow = head, *fast = head; 4 5 while ( fast && fast->next ) 6 { 7 slow = slow->next; 8 fast = fast->next->next; 9 if ( slow == fast ) break; 10 } 11 12 return !(fast == NULL || fast->next == NULL); 13 }
找到交点的思路是把其中链表一个链表尾节点与头节点相连,如果有环,则很容发现问题转化为问题3,求有环的链表的第一个在环里的节点。
找到环点:
1 slist* FindLoopPort(slist *head) 2 { 3 slist *slow = head, *fast = head; 4 5 while ( fast && fast->next ) 6 { 7 slow = slow->next; 8 fast = fast->next->next; 9 if ( slow == fast ) break; 10 } 11 12 if (fast == NULL || fast->next == NULL) 13 return NULL; 14 15 slow = head; 16 while (slow != fast) 17 { 18 slow = slow->next; 19 fast = fast->next; 20 } 21 22 return slow; 23 }
方法二.
设两个工作指针p、q,p总是向前走,但q每次都从头开始走,对于每个节点,看p走的步数是否和q一样。比如p从A走到D,用了4步,而q则用了14步。因而步数不等,出现矛盾,存在环.
//if two pointer are equal, but they don't have the same steps, then has a loop 02 int HasLoop(LinkList L) 03 { 04 LinkList cur1 = L; // 定义结点 cur1 05 int pos1 = 0; // cur1 的步数 06 while(cur1){ // cur1 结点存在 07 LinkList cur2 = L; // 定义结点 cur2 08 int pos2 = 0; // cur2 的步数 09 pos1 ++; // cur1 步数自增 10 while(cur2){ // cur2 结点不为空 11 pos2 ++; // cur2 步数自增 12 if(cur2 == cur1){ // 当cur1与cur2到达相同结点时 13 if(pos1 == pos2) // 走过的步数一样 14 break; // 说明没有还 15 else // 否则 16 return 1; // 有环并返回1 17 } 18 cur2 = cur2->next; // 如果没发现环,继续下一个结点 19 } 20 cur1 = cur1->next; // cur1继续向后一个结点 21 } 22 return 0; 23 }
方法三
在环的入口点出断开,从而转换为看两个链表是否有交点的问题(改日补充)
参考资料:http://blog.csdn.net/yiwuxue/article/details/21973079
http://blog.csdn.net/cuit/article/details/35365219