Given a linked list, return the node where the cycle begins. If there is no cycle, return null.
Example
Given -21->10->4->5, tail connects to node index 1,return node 10
Challenge
Follow up:
Can you solve it without using extra space?
题 Linked List Cycle | Data Structure and Algorithm 的升级版,题目要求不适用额外空间,则必然还是使用快慢指针解决问题。首先设组成环的节点个数为 , 链表中节点个数为 . 首先我们来分析下在链表有环时都能推出哪些特性:
从以上两个容易得到的特性可知,在仅仅知道第一次相遇时的节点还不够,相遇后如果不改变既有策略则必然找不到环的入口。接下来我们分析下如何从第一次相遇的节点走到环的入口节点。还是让我们先从实际例子出发,以下图为例。
slow
和fast
节点分别初始化为节点1
和2
,假设快慢指针第一次相遇的节点为0
, 对应于环中的第i
个节点 , 那么此时慢指针正好走了 步,快指针则走了 步,且存在1: . (之所以在i
后面加1是因为快指针初始化时多走了一步) 快慢指针第一次相遇时慢指针肯定没有走完整个环,且慢指针走的步数即为整数个环节点个数,由性质1和性质2可联合推出。
现在分析下相遇的节点和环的入口节点之间的关联,要从环中第i
个节点走到环的入口节点,则按照顺时针方向移动2: 个节点 ( 为某个非负整数) 即可到达。现在来看看式1和式2间的关系。由式1可以推知 . 从头节点走到环的入口节点所走的步数可用 表示,故在快慢指针第一次相遇时让另一节点从头节点出发,慢指针仍从当前位置迭代,第二次相遇时的位置即为环的入口节点!
由于此题快指针初始化为头节点的下一个节点,故分析起来稍微麻烦些,且在第一次相遇后需要让慢指针先走一步,否则会出现死循环。
对于该题来说,快慢指针都初始化为头节点会方便很多,故以下代码使用头节点对快慢指针进行初始化。
/**
* Definition of ListNode
* class ListNode {
* public:
* int val;
* ListNode *next;
* ListNode(int val) {
* this->val = val;
* this->next = NULL;
* }
* }
*/
class Solution {
public:
/**
* @param head: The first node of linked list.
* @return: The node where the cycle begins.
* if there is no cycle, return null
*/
ListNode *detectCycle(ListNode *head) {
if (NULL == head || NULL == head->next) {
return NULL;
}
ListNode *slow = head, *fast = head;
while (NULL != fast && NULL != fast->next) {
fast = fast->next->next;
slow = slow->next;
if (slow == fast) {
fast = head;
while (slow != fast) {
fast = fast->next;
slow = slow->next;
}
return slow;
}
}
return NULL;
}
};
fast
置为头节点,并只走一步,直至快慢指针第二次相遇,返回慢指针所指的节点。第一次相遇的最坏时间复杂度为 , 第二次相遇的最坏时间复杂度为 . 故总的时间复杂度近似为 , 空间复杂度 .