力扣日记:【链表篇】环形链表、环形链表II
日期:2023.8.18,2023.8.21
参考:代码随想录、力扣
141. 环形链表
题目描述
难度:简单
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
提示:
- 链表中节点的数目范围是 [0, 10^4]
- -105 <= Node.val <= 105
- pos 为 -1 或者链表中的一个 有效索引 。
进阶:你能用 O(1)(即,常量)内存解决此问题吗?
题解
/**
* 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 *dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode *fast = head;
ListNode *slow = dummyHead;
while (true) {
if (fast == NULL || fast->next == NULL) {
return false;
} else if (slow == fast) {
return true;
}
fast = fast->next->next;
slow = slow->next;
}
}
};
执行结果
时间复杂度:O(n)
空间复杂度:O(1)
思路总结
- 其实这道题是看了 环形链表II 的题解后再来做的,因此想到用了快慢指针的方法来做
- 定义两个快慢指针,快指针比慢指针每次多走一步。如果有环的话,由于快慢指针进入环后一定是在环中转圈,所以两者会在环内相遇。即如果有环的话,两者会相遇,否则没有环。
- 具体还是看下面 环形链表II 的思路。
142.环形链表II
题目描述
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
提示:
- 链表中节点的数目范围在范围 [0, 10^4] 内
- -10^5 <= Node.val <= 10^5
- pos 的值为 -1 或者链表中的一个有效索引
进阶:你是否可以使用 O(1) 空间解决此题?
题解
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
#define SOLUTION 2
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
#if SOLUTION == 1 // 哈希表(O(n), O(n))
unordered_map<ListNode *, int> cnt;
ListNode *cur = head;
while (true) {
cnt[cur]++;
if (cnt[cur] > 1) { // 遇到第二遍
return cur;
}
if (cur == NULL) { // 遇到尾节点
return NULL;
}
cur = cur->next;
}
#elif SOLUTION == 2 // 快慢指针(O(n),O(1))
// 先找出是否有环
ListNode *fast = head;
ListNode *slow = head;
while (fast != NULL && fast->next != NULL) {
fast = fast->next->next;
slow = slow->next;
if (fast == slow) { // 如果快慢指针相遇了
// 记录相遇位置
ListNode *index1 = fast;
// 定义从头节点出发的指针
ListNode *index2 = head;
// 两指针逐步后移,如果两者相遇,则为环的入口(可数学证明)
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index1;
}
}
return NULL;
#endif
}
};
复杂度
-
哈希表
时间复杂度:O(n)
空间复杂度:O(n) -
快慢指针法
时间复杂度:O(n)快慢指针相遇前,指针(慢指针)走的次数小于链表长度,快慢指针相遇后,两个index指针走的次数也小于链表长度,总体为走的次数小于 2n
空间复杂度:O(1)
思路总结
- 代码随想录这次的讲解真的很好,建议直接看(绝对不是我偷懒不想记笔记了!!!……好吧就是)
- 一开始做的时候,完全没想到用快慢指针判断是否有环(更不说找到入口了),只能拙劣地用哈希表遍历判断,结果就是空间复杂度超过O(1)
- 快慢指针几个注意的点:
- 快慢指针一定是在环里面相遇:如果没有环,链表是直线,快指针永远在慢指针前面。有了环,快指针最后会在环里面转圈,慢指针也是,因此一定是在环里相遇。
- 快指针比慢指针每次多走一个节点,以确保一定会相遇,而不会直接跳过:快指针相对慢指针每次多走一个节点,即相当于快指针是一个节点一个节点靠近slow的
- 如何找到环的入口,参考代码随想录,通过定义x、y、z
-

-
相遇时慢指针走的路程为:x+y
-
快指针走的路程为:x+y+n(y+z). 其中n是快指针多走的圈数,n>=1
-
由于时间相同,而快指针速度是慢指针2倍,由s/v=t得
-
(x+y+n(y+z))/2=x+y
-
可得:x = z + (n-1)(y+z)
-
上式的意义在于:从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。这也是代码中找到入口的依据。
-
- 还是卡哥讲得清楚些。
文章讲述了如何使用快慢指针方法检测链表是否存在环以及找到环的入口,通过定义两个指针在链表中移动,快指针每次多走一步,如果相遇则存在环,相遇时的位置即为环的入口。
2213

被折叠的 条评论
为什么被折叠?



