LeetCode 141. Linked List Cycle ∈ 剑指23. 链表中环的入口节点 && LeetCode 142. Linked List Cycle II

题目

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

Given a linked list, return the node where the cycle begins. If there is no cycle, return null.

To represent a cycle in the given linked list, we use an integer pos which represents the position (0-indexed) in the linked list where tail connects to. If pos is -1, then there is no cycle in the linked list.

Note: Do not modify the linked list.

Example 1:

Input: head = [3,2,0,-4], pos = 1
Output: tail connects to node index 1
Explanation: There is a cycle in the linked list, where tail connects to the second node.

Example 2:

Input: head = [1,2], pos = 0
Output: tail connects to node index 0
Explanation: There is a cycle in the linked list, where tail connects to the first node.

Example 3:

Input: head = [1], pos = -1
Output: no cycle
Explanation: There is no cycle in the linked list.


这道题,经典题型,某次随性面试居然面到这个,隐约中有印象是快慢指针但是具体忘了,结果在那儿硬想了半天想不到解决方案。刚刚仔细想了一下,发现自己没有思路的原因居然是,对这个链表有误解——我总觉得它可以打个转又继续往后,就像一根绳子一样,比如这样:_____O______。然而事实是,这特么就是一个普普通通的单向链表,只能有一个next啊!所以有环的情况下,就是在链表的最后,又拐了个弯回到了之前走到的某个节点,比如这样:_____O

想要求出环的入口节点,首先你得知道这个链表有没有环。怎么判断有没有环呢?快慢指针!快指针走两步,慢指针走一步,如果走着走着快指针指向了NULL,那完了,这个链表截止了,不可能有环了。那怎样才能早早判断出它有环,让我们跳出这个循环呢,那就等着快慢指针相遇了——它们总会在圈内相遇的,因为最后大家都在转圈圈。

解决了判断有没有环的问题,下一步要开始计算环的入口节点了,还是祭出快慢指针大法。假如已经知道了环的长度n,那么只需要让快指针先在链表上走n个节点,然后两个指针一起在链表上同速移动,当慢指针挪动到了环的入口的时候,快指针就在环里走了一圈回到了环的入口,两个指针就相遇了。这里刚开始不是很懂为什么如此奇妙,后来自己算了一下,假设链表的长度是m,那么入口节点是m-n,第二次慢指针走到入口就相当于走了m-n步,加上快指针第一次走的n步,最后就是走了m步,把整个链表都走完了并回到了环的入口节点。

那么接下来问题来了,我咋知道环有多长呢?那就只能在第一次判断有没有环的时候,快慢指针相遇的时候记录下相遇的节点,然后一步步挪,挪到回到之前记下的节点,这之间的步数就是环的长度了。

思路有了而且好像还算简单,但是写起代码来被各种bug气到爆炸了。纠结了好久好久好久好久,最后发现bug居然是在初始化的时候把p1和p2都设成了头节点,然后在立马接下来的while循环里的条件是while (p1 != p2),那么就根本没进这个循环啊……真是气哭了!解决这个问题以后似乎就变得顺利了…………另外又在讨论版里看到有其他人提出来的做法,是不需要求出环的长度就可以求入口节点的,下面摘录一下吧:

x为不带环的长度,c为环的长度,a为相遇时多走的环中的路。

第一次相遇时:

快指针走过的路程s1 = x + m * c + a

慢指针走过的路程s2 = x + n * c + a

而由于两个指针的时间相同、快指针是慢指针的两倍速度,则s1 = 2 * s2

即,x + m * c + a = 2 * (x + n * c) + a

化简得:x = (n - 2 * m )*c - a = (n - 2 *m -1 )*c + c - a

说明环前的路程是环的长度的k倍再多出橙色路段的距离。

于是,如果把一个指针赶回链表的头节点,再让两个指针同时同速开始挪动,当回到头节点的指针走了x,也就是到了入口节点时,还在环里的指针也多走了x = c - a,也回到了入口节点,于是两个指针在环的入口相遇了。

这个解释通过公式推导非常有理有据,也更容易理解。代码两种都写了,其中注释掉的部分是第二种方法的代码。LC上,时间12ms,99.33%;空间9.8M,41.85%。

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if (!pHead || !pHead->next || !pHead->next->next) {
            return NULL;
        }
        
        ListNode* p1 = pHead->next->next;
        ListNode* p2 = pHead->next;
        
        // 确定是否有环
        while (p1 != p2) {
            if (p1->next && p1->next->next && p2->next) {
                p1 = p1->next->next;
                p2 = p2->next;
            }
            else {
                return NULL;
            }
        }
        
        /*p1 = pHead;
        while (p1 != p2) {
            p1 = p1->next;
            p2 = p2->next;
        }*/
        
        // 求环的长度
        int len = 1;
        ListNode* meet_node = p1;
        while (p1->next != meet_node) {
            len++;
            p1 = p1->next;
        }
        
        p1 = pHead;
        p2 = pHead;
        // 挪动p1 len步
        for (int i = 0; i < len; i++) {
            p1 = p1->next;
        }
        // 同时挪动p1、p2直到相遇
        while (p1 != p2) {
            p1 = p1->next;
            p2 = p2->next;
        }
        
        return p1;
    }
};

看了牛客的讨论版,发现还有一种需要修改原始链表的做法,叫做断链法。思路大概是每次经过一个节点,都把它和下一个节点断开来如下:

设置两个相邻的指针,姑且也叫它快慢指针吧,两个指针每次都往后挪一个节点,在挪动的同时也断掉两个指针之间的next,即让慢指针的next指向NULL,直到没有下一个节点为止,剩下的就是入口起点了。代码写起来还算好写,但按照自己的思路写的时候还是有点小毛病,刚开始写的时候while的循环条件是p1->next != NULL,也就是说当p1没有后续的时候,那么返回p1作为入口节点,但是这样会段错误,想了半天还是没怎么搞清原因。后来按照标准代码,也就是循环条件是p1 != NULL,最后返回p2,发现这样做的话把最后从尾节点返回去的那个next也断掉了,然后最后p1经过一个p1->next这个好像next已经不存在了?这个地方还是没太搞懂,留个坑以后慢慢填吧。代码:

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if (!pHead || !pHead->next) {
            return NULL;
        }
        
        ListNode* p1 = pHead->next;
        ListNode* p2 = pHead;
        
        while (p1) {
            p2->next = NULL;
            p2 = p1;
            p1 = p1->next;
        }
        return p2;
    }
};

2019.12.2补充:

重新写了一下这个代码,发现思路和之前完全不一样了,感觉现在写的更简洁了。先贴141的代码。

Runtime: 12 ms, faster than 73.63% of C++ online submissions for Linked List Cycle.

Memory Usage: 9.7 MB, less than 96.05% of C++ online submissions for Linked List Cycle.

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode* p1 = head;
        ListNode* p2 = head;
        while (p1 && p1->next) {
            p1 = p1->next->next;
            p2 = p2->next;
            if (p1 == p2) {
                return true;
            }
        }
        return false;
    }
};

142

Runtime: 20 ms, faster than 20.49% of C++ online submissions for Linked List Cycle II.

Memory Usage: 9.6 MB, less than 100.00% of C++ online submissions for Linked List Cycle II.

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* p1 = head;
        ListNode* p2 = head;
        while (p1 && p1->next) {
            p1 = p1->next->next;
            p2 = p2->next;
            if (p1 == p2) {
                p1 = head;
                while (p1 != p2) {
                    p1 = p1->next;
                    p2 = p2->next;
                }
                return p1;
            }
        }
        return NULL;
    }
};

2022.11.7

嗯,还是记得141的解法的,快慢指针嘛。但是代码写出来的不够简洁,刚开始想的先判断fast是否等于slow然后再挪,于是因为最开始都初始化成head了导致需要特殊处理,后来看了笔记才意识到可以换一下顺序解决……

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                return true;
            }
        }
        return false;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值