环形链表习题

题目

环形链表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圈后到入环口的距离一致。
第二个方法将该题的解法换为了求两个链表的相交点,虽然也可以求出来,但是求出两个指针相遇点后,还需要求两个链表的相交点,程序开销相比于第一个方法大了很多。不过通过这个思路我们还可以在求两个链表相交点时,将链表变为带环的链表,这样只需求入环口就可以得到两个链表的相交点了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值