题目:环形链表I
答案:
参考链接:https://leetcode-cn.com/problems/linked-list-cycle/solution/huan-xing-lian-biao-by-leetcode/
1.哈希表
遍历所有结点并在哈希表中存储每个结点的引用(或内存地址)。如果当前结点为空结点 null(即已检测到链表尾部的下一个结点),那么我们已经遍历完整个链表,并且该链表不是环形链表。如果当前结点的引用已经存在于哈希表中,那么返回 true(即该链表为环形链表)。
public class Solution {
public boolean hasCycle(ListNode head) {
Set<ListNode> nodesSeen = new HashSet<>();
while (head != null) {
if (nodesSeen.contains(head)) {
return true;
} else {
nodesSeen.add(head);
}
head = head.next;
}
return false;
}
}
注:使用HashSet而非HashMap的原因:HashSet可以只有一个参数
时间复杂度:O(n)
空间复杂度:O(n)
2.双指针
通过使用具有不同速度的快、慢两个指针遍历链表。慢指针每次移动一步,而快指针每次移动两步。
如果列表中不存在环,最终快指针将会最先到达尾部,返回 false。
如果存在环,则快指针一定会追上慢指针(如果快指针比慢指针慢一步,在下一次迭代中,分别走两步及一步并相遇;如果快指针比慢指针慢2–3步甚至更多,在之后的迭代中也会变成只慢一步)(每走1轮,fast 与 slow 的间距 +1,fast 终会追上 slow)
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
时间复杂度:O(n)
空间复杂度:O(1)
题目:环形链表II
答案:
1.哈希表
用一个 Set 保存已经访问过的节点,我们可以遍历整个列表并返回第一个出现重复的节点
public class Solution {
public ListNode detectCycle(ListNode head) {
Set<ListNode> nodesSeen = new HashSet<>();
while (head != null) {
if (nodesSeen.contains(head)) {
return head;
} else {
nodesSeen.add(head);
}
head = head.next;
}
return null;
}
}
时间复杂度:O(n)
空间复杂度:O(n)
2.双指针
转自:https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/linked-list-cycle-ii-kuai-man-zhi-zhen-shuang-zhi-/
算法流程:
1.双指针第一次相遇:设两指针 fast,slow 指向链表头部 head,fast 每轮走2步,slow 每轮走1步;
第一种结果:fast指针走过链表末端,说明链表无环,直接返回null
第二种结果:fast == slow时,两指针第一次相遇。
设链表共有a+b个节点,其中链表头部到链表入口 有a个节点(不计链表入口节点), 链表环 有b个节点(这里需要注意,a和b是未知数);设两指针分别走了f,s步,则有:
(1)fast 走的步数是slow步数的 2倍,即 f = 2s;
(2)fast 比 slow多走了 n个环的长度,即 f = s + nb;( 解析: 双指针都走过 a 步,然后在环内绕圈直到重合,重合时 fast 比 slow 多走环的长度整数倍 )
以上两式相减得:f = 2nb,s = nb,即fast和slow 指针分别走了 2n,n 个 环的周长 (注意: nn 是未知数,不同链表的情况不同)
2.目前情况分析:
所有走到链表入口结点的步数是:k=a+nb(先走a步到入口节点,之后每绕 1 圈环( b 步)都会再次到入口节点)
而目前,slow 指针走过的步数为 nb 步。因此,我们只要想办法让 slow 再走 a 步停下来,就可以到环的入口。
3.双指针第二次相遇
slow指针位置不变 ,将fast指针重新指向链表头部节点 ;slow和fast同时每轮向前走 1 步;
TIPS:此时 f = 0,s = nb ;
当 fast 指针走到f = a时,slow 指针走到s = a+nb,此时两指针重合,并同时指向链表环入口
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head, slow = head;
while (true) {
if (fast == null || fast.next == null) return null;
fast = fast.next.next;
slow = slow.next;
if (fast == slow) break;
}
fast = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return fast;
}
}
时间复杂度:O(n)
空间复杂度:O(1)