进阶:
你能否不使用额外空间解决此题?
方案1 哈希表
哈希表是最容易理解的一个方案
建立一个哈希表,如果不存在就向哈希表中添加数据,存在的话就直接返回true(存在的可能只有P点,同时P点也是环的入点(这个和下一道题有关))
缺点:占用大量的空间,实际中,一个链表中的数据是很多的,这时候你建立一个哈希表,就会重新建一个存在大量数据的额外空间。代码是给电脑看的,人虽然容易理解,但是电脑不在乎容易不容易,它在乎的时间和空间的使用情况,所以这个方法不是最佳的。时间复杂度虽然是O(n),同样空间复杂度也是O(n)
function hasCycle(head) {
let nodesSeen = new Set()
while (head != null) {
if (nodesSeen.has(head)) {
return true;
} else {
nodesSeen.add(head);
}
head = head.next;
}
return false;
}
方案2 快慢指针
建立两个指针fast和slow
每一次循环fast移动两个位置
fast=fast.next.next
slow移动一个位置
slow=slow.next
当slow==fast的时候,就说明这个链表中存在环,不要问为什么?自己动手画个图,按着代码走一遍就知道,看是看不懂得,我就不说明了
优点:他的时间复杂度也是O(n)和上面的哈希表是一样的,但是它的空间复杂度是O(1)的,远远小于哈希表的O(n),虽然但是人不容易理解,但是机器是不在乎的,你的代码是让机器运行的。
var hasCycle = function(head) {
if (head == null || head.next == null) {
return false
}
let fast = head.next
let slow = head
while (fast != slow) {
if (fast == null || fast.next == null) {
return false;
}
fast = fast.next.next
slow = slow.next
}
return true
};
方案3 在链表中增加一个域visited
在链表中增加一个域visited(可以是a,b,c,d,…),初始化都为0,从链表的头部开始走,每走过一个链表就标记visited为1,如果要访问的下一个节点的visited域为1,那么证明链表中有环。
上面的快慢指针和哈希表都是需要额外的空间复杂度,进阶不需要额外的空间复杂度,这里就实现一下
var hasCycle = function(head) {
while (head) {
if (head.visited) return true
head.visited = true
head = head.next
}
return false
};
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
说明:不允许修改给定的链表。
进阶:
你是否可以不用额外空间解决此题?
方案1 哈希表
这个就是判断是否有环,有环的地方就是环的节点P 同样他的缺点是需要大量的额外空间复杂度,优点就是人容易理解(没什么卵用,代码让机器运行)
var detectCycle = function(head) {
if (head == null || head.next == null) {
return null
}
let hasObj = new Set()
while (head != null) {
if (hasObj.has(head)) {
return head
} else {
hasObj.add(head)
}
head = head.next
}
};
方案2 快慢指针
这里前半部分和上面判断是否有环一样,当有环的时候,fast==slow。这时候fast返回头结点head
fast=head
slow指针不变在原地
然后fast和slow指针每次移动一个位置,当fast==slow的时候,他们就在环的入口位置。不要问我为什么,自己走一遍,网上的图你看也看不懂。
fast=fast.next
slow=slow.next
优点还是那样:只需要O(1)的额外的空间复杂度
方法3 在链表中增加一个域visited
道理同上面的那个方案,只是返回值不同。其实这种方法和哈希表一样,只是他们的是在原链表中添加了一个点而已
var detectCycle = function (head) {
while (head) {
if (head.visited) return head
head.visited = true
head = head.next
}
return null
};