题目
环形链表II
题目要求
示例
解答
方法一、
快慢指针法
实现思路
可以使用两个指针fast与slow,它们起始都指向链表的头部。随后,slow指针每次向后移动一个位置,而fast指针每次向后移动两个位置,这样如果链表中存在环,则fast指针最终将再次与slow指针在环中相遇,以此可以判断链表中是否有环。
当fast指针追上slow指针后,此时设从链表头结点到入环的第一个结点的距离为a,入环的第一个结点的位置到fast和slow相遇的位置的距离为b,fast和slow相遇的位置到入环的第一个结点的距离为c。则fast从头结点出发到在环内和slow相遇,共走了 a + n*(b+c) + b的路程(n为绕环的圈数),而slow从头结点出发到在环内和fast相遇,共走了 a + b的路程。而fast走的路程是slow的2倍,所以有 2*(a+b) = a+n*(b+c)+b 。化简为 a = (n-1)(b+c) + c。即从头结点到入环点的距离等于fast和slow相遇点到入环点的距离加n-1圈的环长。所以可以fast和slow相遇时,设置一个指针start指向头结点,然后它和slow指针每次向后移动一个位置,最终它们会在入环点相遇。可能这个过程中slow指针又绕环转了几圈,但是最后slow会和start在入环点相遇。
时间复杂度和空间复杂度
时间复杂度:O(N)
空间复杂度:O(1)
代码
struct ListNode *detectCycle(struct ListNode *head) {
if(head==NULL||head->next==NULL)
{
return NULL;
}
struct ListNode * slow = head;
struct ListNode * fast = head;
//先判断是否存在环
while((fast!=NULL)&&(fast->next!=NULL))
{
slow=slow->next;
fast=fast->next->next;
if(slow==fast) //如果slow和fast相遇,说明存在环
{
struct ListNode * start = head; //设置一个指向头结点的指针
while(start!=slow) //start和slow每次都向后移一位,最终,两个指针会在入环点相遇,此时两个指针都指向入环结点
{
start=start->next;
slow=slow->next;
}
return slow;
}
}
//如果fast=NULL或fast->next=NULL,则链表中不存在环。
return NULL;
}
方法二、
变为两链表找相交点
实现思路
当找到fast和slow指针相遇的结点后,将该结点的next置为NULL,此时环就断了,变为了两个链表在入环点相交。此时从两个链表的头结点开始访问,找到两个链表的相交结点就是入环点。
即变为这样,此时问题变为求两个链表的相交点。
时间复杂度和空间复杂度
该方法还需要求两个链表的相交结点,时间复杂度比第一个方法大得多。
代码
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if(headA==NULL||headB==NULL)
{
return NULL;
}
struct ListNode * currA = headA;
struct ListNode * currB = headB;
while(currA!=currB)
//currA和currB将两个表都遍历一遍,此时currA和currB遍历的结点数一样,为A+B的结点数,
//所以如果A和B有相交结点时,此时currA==currB,会跳出循环,此时currA和currB指向的都是相交的首结点
//如果遍历到最后还没有跳出循环,则就currA和currB都指向NULL时跳出循环,此时没有相交结点,返回NULL。
{
currA = currA==NULL ? headB : currA->next;
currB = currB==NULL ? headA : currB->next;
}
return currA;
}
struct ListNode *detectCycle(struct ListNode *head) {
if(head==NULL||head->next==NULL)
{
return NULL;
}
struct ListNode * slow = head;
struct ListNode * fast = head;
//先判断是否存在环
while((fast!=NULL)&&(fast->next!=NULL))
{
slow=slow->next;
fast=fast->next->next;
if(slow==fast) //如果slow和fast相遇,说明存在环
{
//将环断开,slow和fast相遇点的next结点为新链表的头结点
struct ListNode * list2 = slow->next;
slow->next=NULL;
struct ListNode * list1 = head;
//此时问题变为求list1和list2链表的相交点。
return getIntersectionNode(list1,list2);
}
}
//如果fast=NULL或fast->next=NULL,则链表中不存在环。
return NULL;
}
总结
通过第一个方法设置两个快慢指针,可以很容易的判断链表是否带环,然后通过公式的推导,可以得出结论链表头结点到入环口的距离等于快慢指针相遇点绕环n圈后到入环口的距离一致。
第二个方法将该题的解法换为了求两个链表的相交点,虽然也可以求出来,但是求出两个指针相遇点后,还需要求两个链表的相交点,程序开销相比于第一个方法大了很多。不过通过这个思路我们还可以在求两个链表相交点时,将链表变为带环的链表,这样只需求入环口就可以得到两个链表的相交点了。