面试高频题の环形链表 环形链表Ⅱ 全解析

🎐 前情提要

本题传送门:
141. 环形链表
142. 环形链表 II

以下是我自己对这道题的一些见解与看法~


👩🏻‍🏫作者: 初入编程的菜鸟哒哒
📚系列文章目录:

一、TvT——JavaSE(1)–Java的数据类型与变量
二、TvT——JavaSE(2)–Java的运算符
三、TvT——JavaSE(3)–Java的逻辑控制
四、TvT——JavaSE(4)–Java方法的使用
五、你真的学会了JavaSE中数组的定义与使用吗?
六、Java中的类与对象来袭
七、类与对象有关static成员的知识你真的了解吗?
八、有关代码块的知识你真的了解吗?


📜刷题笔记:
1️⃣TvT——C语言进阶の刷题【1】:指针进阶



🎪1. 环形链表

在这里插入图片描述

🎠1.1 环形链表题目描述

题目描述:

在这里插入图片描述

示例 1:

在这里插入图片描述

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:
在这里插入图片描述

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:
在这里插入图片描述

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

提示:

  • 链表中节点的数目范围是 [0, 104]
  • -105 <= Node.val <= 105
  • pos-1 或者链表中的一个 有效索引

进阶:
你能用 O(1)(即,常量)内存解决此问题吗?

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/linked-list-cycle

在这里插入图片描述

🎠1.2 环形链表分析

我们首先想能否用一个指针来做:

正常的链表我们判断链表结束是判断指针是否指向空或者指向头,那如果找不到头或者空是否就可以证明这个链表是一个环形链表呢?:
在这里插入图片描述

但是这里显然不行,因为尾指针并没有指向头或者空,我们不知道指向了哪里…因此无法写循环结束的条件,所以这个循环就是一个死循环,而我们没法在代码里判断了这个循环是一个死循环所以是环形链表,这是做不到的。
在这里插入图片描述

好,那一个指针解决不了问题,我们就想一想能不能用两个指针来解决呢?

答案是可行的,使用快慢指针就可以完美解决:

我们定义两个指针,一个慢指针一个快指针,慢指针一次走一步,快指针一次走两步:

如果链表不是环形链表,那快指针与慢指针永远都不会 相遇 ~

因为快指针永远都会先走到指向空的位置结束。
在这里插入图片描述
而链表是环形链表时,快指针和慢指针就会相遇!
在这里插入图片描述
❓ 为什么呢?

是因为慢指针一次走一步,快指针一次走两步,在慢指针进入循环圈后,快指针走的是慢指针的两倍,就可以理解为快指针每次都比慢指针多走一个结点,所以快指针最终总会赶上慢指针。

在这里插入图片描述
这样更加直观,可以看见fast一直在追slow

好了,我们知道怎么做了,可以用代码实现了。

在这里插入图片描述

🎠1.3环形链表代码实现

时间复杂度:O(N)
空间复杂度:O(1)

这里我是用java、C和C++实现的:
java:

public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null||head.next == null) {
            return false;
        }
        ListNode slow = head,fast = head;
        while(fast != null&&fast.next != null) {
            slow = slow.next;
            fast = (fast.next).next;
            if(slow == fast) {
                return true;
            }
        }
        return false;
    }
}

在这里插入图片描述

C:

bool hasCycle(struct ListNode *head) {
    if(head == NULL||head->next ==NULL) {
        return false;
    }
    struct ListNode * fast = head;
    struct ListNode * slow = head;
    while(fast!=NULL&&fast->next != NULL) {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast) {
            return true;
        }
    }
    return false;
}

在这里插入图片描述

C++:

class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head == NULL||head->next ==NULL) {
            return false;
        }
        struct ListNode * fast = head;
        struct ListNode * slow = head;
        while(fast!=NULL&&fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
            if(slow == fast) {
                return true;
            }
    }
    return false;
    }
};

在这里插入图片描述
在这里插入图片描述

🎠1.3 环形链表进阶证明(面试重点)

  1. slowfast一定会在环里相遇吗?有没有fast追不上slow情况?
  2. 思考慢指针一次走一步,快指针一次走3步、4步…n步,快指针也可以追上慢指针吗?
  3. 如何求环的入口点?

1️⃣

slow一次走一步,fast一次走2步一定可以追上。

假设slow进环了之后,fast正式开始追,fastslow之间的距离是N。
追的过程中,他们之间的距离是如何变化的?

N
N-1
N-2
N-3

0

当距离缩小到0的时候,就相遇了,一定不会错过的,因为每次的距离都是缩小1

2️⃣

如果slow每次走一步,fast每次走3步,不一定能追上,甚至会永远追不上。

我们仿照上面的想法再来推一下:

假设slow进环了之后,fast正式开始追,fastslow之间的距离是N。
追的过程中,他们之间的距离是如何变化的?

N
N-2
N-4
N-6

0或者(-1)

为什么会出现-1呢?

分析一下:

如果N是2的倍数,一直减偶数确实会减为0,但如果N是奇数(不是2的倍数)的话,最后就会被减到 -1 ,那就错过了!!fast反超了slow一个结点!
在这里插入图片描述
那有没有可能下一圈就可以追上了呢?

fast反超了slow,那么就进入新的追逐了,他们之间的距离是C-1(假设C是环的长度)

如果C-1是奇数,那就永远也追不上了。
如果C-1是2的倍数,那下一圈可以追上。

因为距离是奇数就意味着永远会反超1步,快追上时还是会反超1步。

fast每次走4步也是一样的道理,如果N是4的倍数才可以在第一次追上,如果N不是4的倍数就要求C-24的倍数,不然也永远追不上。

🎪2.环形链表II

在这里插入图片描述

🎠2.1 环形链表II题目描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/linked-list-cycle
在这里插入图片描述

🎠2.2 环形链表II(如何求环的入口点)分析

3️⃣如何求环的入口点:
在这里插入图片描述
在这里插入图片描述

结论:

一个指针从`meet`点开始走,一个指针从链表开始走,他们会在入口点相遇。

那我们来推一下,这个结论是怎么得到的呢?

首先我们知道快指针走的路程是慢指针走的路程的二倍。

slow进环了之后,在一圈之内,fast一定会追上slow

为什么?

因为slow进环的时候和fast的距离不可能大于一圈:
在这里插入图片描述
此刻fastslow都在圈里,距离怎么可能大于一圈呢?
如果slow走了一圈,那fast都已经走了两圈了,此时fast走的路程是2Cslow走的路程是C,它们之间的路程差C,而一开始的距离N小于C的,路程差都小于开始的距离了,怎么可能在这之前没有相遇过呢?

假设环外的路程Lslow进环之后到meet距离x,假设环的长度C
在这里插入图片描述
那么:

slow走的路程: L+x

fast走的路程:L+n*C+x

我们先拿n = 1的情况举例讲解:
在这里插入图片描述
红线是fast走的距离,可以看见slow进环的时候fast已经在环里走一段路程了,最后追上slow一定会至少走上一个环的距离。
这种情况fast走的路程就是L+C+x

那为什么会有n*c的情况呢?因为在slow进环之前,fast不一定在环里只走了一圈。
例如这种前缀很长环很小的情况:
在这里插入图片描述
如果入环前的长度越长,环越小,n越大。
如果入环前的长度小于环的大小,那n就是1。

那我们就可以得到式子:

L+ n * C+ x = 2 * (L+ x)
🔽

n * C = L + x
🔽
n * C - x = L
🔽
( n - 1 ) * C + C - x = L

( n - 1 ) * C就是fast走n圈了之后又回到进圈起始点的位置,此时与n = 1 时的情况位置是一样的,然后fast再走L+C+x,与slow相遇,所以这种情况我们可以直接想象成n = 1,然后就能推出:

C-x = L

在这里插入图片描述

就得到了结论:

一个指针从meet点开始走,一个指针从链表开始走,他们会在入口点相遇。

🎠 2.3 环形链表 II 代码实现

时间复杂度:O(N)
空间复杂度:O(1)

java实现:

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head == null||head.next == null) {
            return null;
        }
        ListNode slow = head,fast = head;
        //一开始flag定义为无环
        boolean flag = false;
        while(fast!=null&&fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if(slow == fast) {
                //有环
                flag = true;
                break;
            }
        }
        if(flag == false) {
            return null;
        }
        slow = head;
        while(slow != fast) {
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
}

在这里插入图片描述
C:

struct ListNode *detectCycle(struct ListNode *head) {
    if(head == NULL||head->next == NULL) {
            return NULL;
        }
        struct ListNode* slow = head;
        struct ListNode* fast = head;
        //一开始flag定义为无环
        bool flag = false;
        while(fast != NULL&&fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
            if(slow == fast) {
                //有环
                flag = true;
                break;
            }
        }
        if(flag == false) {
            return NULL;
        }
        slow = head;
        while(slow != fast) {
            slow = slow->next;
            fast = fast->next;
        }
        return slow;
    }

在这里插入图片描述
C++:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
         if(head == NULL||head->next == NULL) {
            return NULL;
        }
        struct ListNode* slow = head;
        struct ListNode* fast = head;
        //一开始flag定义为无环
        bool flag = false;
        while(fast != NULL&&fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
            if(slow == fast) {
                //有环
                flag = true;
                break;
            }
        }
        if(flag == false) {
            return NULL;
        }
        slow = head;
        while(slow != fast) {
            slow = slow->next;
            fast = fast->next;
        }
        return slow;
    }
};

在这里插入图片描述

🎪总结

在这里插入图片描述

你可以叫我哒哒呀
本篇到此结束
“莫愁千里路,自有到来风。”
我们顶峰相见!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值