✨本题传送门:
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 环形链表进阶证明(面试重点)
- 那
slow
和fast
一定会在环里相遇吗?有没有fast追不上slow情况? - 思考慢指针一次走一步,快指针一次走3步、4步…n步,快指针也可以追上慢指针吗?
- 如何求环的入口点?
1️⃣
slow 一次走一步,fast 一次走2步一定可以追上。 |
假设slow
进环了之后,fast
正式开始追,fast
和slow
之间的距离是N。
追的过程中,他们之间的距离是如何变化的?
N
N-1
N-2
N-3
…
0
当距离缩小到0的时候,就相遇了,一定不会错过的,因为每次的距离都是缩小1。
2️⃣
如果slow 每次走一步,fast 每次走3步,不一定能追上,甚至会永远追不上。 |
我们仿照上面的想法再来推一下:
假设slow
进环了之后,fast
正式开始追,fast
和slow
之间的距离是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-2
是4的倍数,不然也永远追不上。
🎪2.环形链表II
🎠2.1 环形链表II题目描述
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/linked-list-cycle
🎠2.2 环形链表II(如何求环的入口点)分析
3️⃣如何求环的入口点:
结论:
一个指针从`meet`点开始走,一个指针从链表开始走,他们会在入口点相遇。 |
那我们来推一下,这个结论是怎么得到的呢?
首先我们知道快指针走的路程是慢指针走的路程的二倍。
slow
进环了之后,在一圈之内,fast
一定会追上slow
。
为什么?
因为slow进环的时候和fast的距离不可能大于一圈:
此刻fast
和slow
都在圈里,距离怎么可能大于一圈呢?
如果slow
走了一圈,那fast
都已经走了两圈了,此时fast
走的路程是2C
,slow
走的路程是C
,它们之间的路程差是C
,而一开始的距离N是小于C的,路程差都小于开始的距离了,怎么可能在这之前没有相遇过呢?
假设环外的路程为L
,slow
进环之后到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;
}
};