Singly-Linked List Oj - Circle - list
条件
* Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */
环形链表
成环链表与不成环链表相比,有一个很明显的差异
- 成环链表中是不存在尾节点的
- 所有节点中的指针都是指向下一节点的
根据这个差异,便可以很好的分辨链表分辨出来
该如何利用这个差异呢?
- 若在一个成环链表中寻其尾节点,一定会出现死循环
- 但设计程序时,也不能将程序设计成死循环。如果利用快慢指针,便可以很好的解决这个问题。
给定一个链表,判断链表中是否有环
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/linked-list-cycle-ii
思路
我们需要做的就是判断是否有环,所以可以定义一个fast 和 slow快慢指针,快指针每次走两步,慢指针每次走一步,如果快指针可以走到NULL,即无环,如果快指针一直走但是走不到头,就是有环,需要判断的是,快指针必定会比慢指针先多走一圈而追上慢指针,即相等。
代码
bool hasCycle(struct ListNode *head) { struct ListNode *fast = head, *slow = head; //fast->next和fast都应该不为空才行 while (fast && fast->next) { fast = fast->next->next; slow = slow->next; if (fast == slow) { return true; } } return false; }
延伸问题
为什么slow和fast一定在环中相遇,会不会在环里面错过,永远遇不上,证明一下。
- 带环则一定会相遇
- 分析证明:
slow和fast的距离差始终为n,n为slow走过的步数,n从1开始增加,为正整数。每追一次,距离减少1,他们之间的距离最后都会减到0,这个点就是相遇的点。
为什么slow走一步,fast走两步呢,能不能fast走n步呢,证明一下。
如果slow走一步,fast走三步,那么每次slow走一步,它们会相差2步,slow刚进环之后假设距离差为N,如果N为奇数,则距离会变为N-2,N-4,...,1,-1。-1就代表他们之间的距离为C-1(C为环长)。
- 如果C-1为奇数,那么就永远追不上了
- 如果C-1为偶数,那么可以追上。
假设slow一次走一步,fast一次走x步,以此类推
-
那么就是(x-1)步为每次的差值,假设slow刚进环的二者距离差为N,N-n(x-1),如果N是(x-1)的倍数,就可以追上。如果不是,比如N = n(x-1)-1,就是代表他们两个此刻的距离为C-1,如果C-1是(x-1)的倍数,就可以追上,否则,无法追上。
-
总结一下:
- 如果N是步数差的整数倍,可以追上。
- 如果第一次追不上,那么会出现fast掠过slow但并不相遇。可能会出现从-1、-2、...、-(x-2)的情况。
- 当环长C加上上面的负数为步数差的整数倍时,也可以追上。
给定一个链表,返回链表开始入环的第一个节点
延伸问题
slow一次走一步,fast一次走两步,一定会相遇,如何求环的入口点呢?
-
结论:
-
-
一个指针从相遇点开始走,另一个指针从链表头开始走(都是每次走一步),它们会在环的入口点相遇。
-
两个指针同时从链表头开始走,第一次相遇时,走过的路程差就是环长C的整数倍。
-
思路1
用上面的结论,即先找相遇点->再双指针求交点,找到的交点就是换的入口点。
代码1
struct ListNode *detectCycle(struct ListNode *head) { struct ListNode *slow = head, *fast = head; while(fast&&fast->next) { fast = fast->next->next; slow = slow->next; if(slow == fast) { struct ListNode *meetNode = slow; while(meetNode!=head) { meetNode = meetNode->next; head = head->next; } return meetNode; } } return NULL; }
思路2
我们把相遇点断开,让其指向空,会出现两个链表,一个是由原链表头指向相遇点,一个是由原来相遇点的后一个节点指向相遇点的链表。让两个链表求交点。交点即为环节点。(实践起来稍繁琐。)