环形链表I
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
进阶:
你能用 O(1)(即,常量)内存解决此问题吗?
思路一: 快慢指针法
这个有没有环,可以采用快慢指针法,快的一次走两步,慢的一次走一步,如果最后快的会追上慢的,那么就是有环。
class Solution {
public:
bool hasCycle(ListNode *head) {
if (!head || !head->next)
return false;
ListNode *fast = head->next;
ListNode *slow = head;
while (fast != slow)
{
if (!fast || !fast->next) return false;
slow = slow->next;
fast = fast->next->next;
}
return true;
}
};
python实现:
class Solution:
def hasCycle(self, head:ListNode) -> bool:
# 判断空操作
if not head or not head.next:
return False
# 快慢指针
slow = head
fast = head.next
while fast != slow:
if not fast or not slow:
return False
fast = fast.next.next if fast.next else None
slow = slow.next
return True
思路二:哈希表法
在遍历的过程中,用set存下元素的地址,如果发现有地址已经在set里面,那么就说明有环,又回去了
class Solution {
public:
bool hasCycle(ListNode *head) {
if (!head || !head->next) return false;
ListNode *p = head;
set<ListNode*> s;
while (p)
{
if (s.count(p))
return true;
s.insert(p);
p = p->next;
}
return false;
}
};
Python版本:
class Solution:
def hasCycle(self, head:ListNode) -> bool:
# 判断空操作
if not head or not head.next:
return False
s = set()
p = head
while (p):
if p in s:
return True
s.add(p)
p = p.next
return False
思路三 - 投机取巧法
这个方法是我的首次a的想法,因为当时没想那么多,直接就跳出了这么个方法,但是这个方法单纯的是解决这个问题,并不是一种思想,也不值得借鉴,但是也反映了解决某个问题的强烈欲望,以完成这个问题为本身,并没有寄希望于举一反三的效果。
就是这样,我直接遍历,虽然我知道,如果有环的话肯定不会停止这个循环,那么我就假设,这里面所有的节点,如果没环的情况下不会超过1万个。所以我设置了一个计数器,如果超过10000了,说明就有环了,没想到竟然能a。
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode *p = head;
int cou = 0;
while (p)
{
cou++;
p = p->next;
if (cou>100000)
return true;
}
return false;
}
};
环形链表 II
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
说明:不允许修改给定的链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。
思路一:快慢指针法
这个快慢指针法其实在考察一个数学上的问题,有环的情况下先上结论:
- 如果快指针和慢指针都从head节点出发,快指针一次走两步,慢指针一次走一步,经历若干圈之后,两者一定会从某个节点相遇, 且快指针走过的路程是慢指针走过的路程的两倍。
- 不仅会相遇,如果这时候快指针回到起始位置,慢指针在相遇位置, 然后,快指针一次走一步,慢指针一次走一步,若干步之后,两者还会相遇,且相遇位置就是入环节点处。
这里使用快慢指针的思路,其实利用了上面的这两个结论。
首先,先解释一下子为什么是这样,结论一不解释, 会相遇这个如果不明白可以想象围着操场追赶,数学证明看LeetCode官方题解。 这里重点解释结论二, 看下面画的草图(A和C是一点,便于区分环):
就假设第一次相遇, 那么fast走的路程是 OA + AB + BC + CB, slow走的路程是OA + AB。 根据结论一, fast的走的路程应该是slow走的路程的两倍。即 OA + AB + BC + CB = OA + AB + OA + AB , A点和C点是同一点,即AB和CB其实是同一回事。 这样上面就成了 OA = BC。
所以,如果在B点fast和slow相遇的话,fast回到原点,slow不动,那么slow和fast以1步的步伐走的时候,到再相遇,slow走的正是BC,fast走的正是OA。 而A正是入环点。
代码如下:
class Solution:
def DetectCycle(self, head:ListNode) -> ListNode:
# 判断空操作
if not head or not head.next:
return None
# 从起始开始走
fast = head
slow = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if (fast == slow): # 相遇
fast = head # fast 回到起点
while fast != slow:
slow = slow.next
fast = fast.next
return slow
return None
思路二: 哈希表法
这个只需要在环形链表I上的那个代码简单修改即可,因为这个的思想是我只有一个指针从head开始,每走一个,检查一下是否这个节点再集合里面,如果在,那就直接返回即可,如果不在,那么就假如这个节点,往后走。
class Solution:
def detectCycle(self, head:ListNode) -> ListNode:
# 判断空操作
if not head or not head.next:
return None
s = set()
p = head
while (p):
if p in s:
return p
s.add(p)
p = p.next
return None
思路三: 投机取巧法 – 这个其实不符合规定,思路没想到
这个其实违反了规定,改变了链表本身,但是这个思路我没有想到,所以记录一下,思想是在值上做文章,我依然从head开始遍历每个节点,遍历的同时,将每个链表节点的值类型改变, 那么如果下一个链表的值的类型与改变后的类型相等,那么这个节点就是入环节点。
下面我每遍历一个节点,就把值的部分改成了字典的类型,其实不符合题目要求了。
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
# 判断空操作
if not head or not head.next:
return None
p = head
loc = 0
while p:
if isinstance(p.val, dict):
return p
elif isinstance(p.val, int):
p.val = {loc:p.val}
p = p.next
loc = loc + 1
return None