给定一个链表的头节点
head,返回链表开始入环的第一个节点。 如果链表无环,则返回null。如果链表中有某个节点,可以通过连续跟踪
next指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数pos来表示链表尾连接到链表中的位置(索引从 0 开始)。如果pos是-1,则在该链表中没有环。注意:pos不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改 链表。
解法1 暴力解法
思路
和上一题的思路类似,上一题是检查是否有环,而现在题目要求返回环形的入口。
环形的入口其实就是哈希表里第一个重复的节点。直接返回即可。
代码
function detectCycle(head: ListNode | null): ListNode | null {
let cur = head;
const set = new Set();
while (cur) {
if (set.has(cur)) {
return cur;
} else {
set.add(cur);
}
cur = cur.next;
}
return null;
};
时空复杂度
时间复杂度:O(n)
空间复杂度:O(n)
解法2 快慢指针
思路
判断是否有环的思路还是沿用上题,首先确认链表是有环的,然后再去思考如何找到环形的入口。
快慢指针会在环形内部相遇,而这个相遇的地点是不确定的,但可以确定的是快慢指针所走的步数。
慢指针走了入口前 x 步和环形内 y 步,即 (x + y) 步。而快指针走了它的两倍 2(x + y) 步。而快指针是在环内转圈遇到慢指针,所以它走的步数是 x + y 步加上在环内的圈数,假设 k 圈,环的长度为 C。
我们可以得到 2(x + y) = x + y + K * C。
移项可以得到 x + y = k * C。所以head指针到环形入口的 x = k * C - y。
C 是整个环的长度,y 是相遇的节点即从环形入口开始走的步数,所以只要将剩下的步数走完,即可走到环形的入口。
此时让 head 指针和 slow 指针同时向后走,相遇的节点就是环形的入口节点。
代码
function detectCycle(head: ListNode | null): ListNode | null {
if (!head || !head.next) {
return null;
}
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
while (slow !== head) {
slow = slow.next;
head = head.next;
}
return slow;
}
}
return null;
};
时空复杂度
时间复杂度:O(n)
空间复杂度:O(1)