目录
一、链表中的双指针
参考文献:https://leetcode-cn.com/explore/learn/card/linked-list/194/two-pointer-technique/743/
二、环形链表
2.1 题目要求
2.2 解决过程
注意事项
内部隐藏了已定义的节点类 ListNode,如顶部注释所示。示例中的输入 head 和 pos 用于说明输入单链表构成 (节点元素及尾部节点引用),不可直接使用。可用的只有作为函数形参的头节点 head,其类型是 ListNode 而非 List。
个人实现
法一:朴素遍历。先判断特殊情况,若单链表中的节点数不大于 1,则不存环。否则,遍历单链表,若某节点的指向的下一节点引用为 None,则表示无环;若为已遍历标记,则表示有环;否则,将当前节点元素覆盖为已遍历标记,如 "OK",从而 实现用 O(1) 内存解决问题 (也可开辟新的内存空间保存已遍历节点避免 in-place 操作,如法二),重复上述操作直至退出循环。空间复杂度 O(1),时间复杂度 O(n)。
2020/07/09 - 56.61% - 说明还有更好的方式,毕竟开头的判断与元素的替换赋值都需要代价 (特别是赋值)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
if (not head) or (head.next == None): # 特殊情况 head=[] 或 head=[1], pos=-1
return False
curr = head # 指向头部
while True: # 遍历链表
if curr.next == None: # 若下一个节点引用为 None, 则无环
return False
elif curr.val == "OK": # 若下一个节点元素为已遍历标记, 则有环
return True
else:
curr.val = "OK" # 否则, 令当前节点保存已遍历标记 "OK"
curr = curr.next # 指向下一个节点
法二:查找集合 / 哈希表。将遍历过的节点 (的引用或内存地址) 存入集合 check (或哈希表) 中,倘若出现了重复的节点,则表明链表存在环。注意,所谓重复节点,不仅要求元素 val 相同,还需引用 next 一致,这就意味着只有同一个节点才会出现重复 (而且多数语言中,相同的值可存在于不同的内存地址)。若遍历到节点的引用为 None,则为链表尾部,表明链表无环。
思想类似法一,但非 in-place 操作,而是开辟了新的内存空间。空间复杂度 O(n),时间复杂度 O(n)。
2020/07/09 - 56.61% - 此法号称次快
class Solution:
def hasCycle(self, head: ListNode) -> bool:
check = set() # 已遍历节点集合 (改用字典-哈希表更好)
curr = head
while curr:
if curr in check: # 出现同一节点表示单链表有环
return True
else:
check.add(curr) # 否则将已遍历节点加入集合
curr = curr.next # 指向下一个节点
return False
法三:双指针 / 快慢指针。如第一节所述,每次循环,令慢指针前进一步、快指针前进两步。若单链表中存在环,则两轮遍历后,快慢指针将重合;否则,快指针将先指向 None 从而退出循环,表示无环。空间复杂度 O(1),时间复杂度 O(n)。
2020/07/09 - 89.28% - 此法号称最快
class Solution:
def hasCycle(self, head: ListNode) -> bool:
slow = fast = head # 快、慢指针起点相同
while fast and fast.next: # 重要循环条件
fast = fast.next.next # 每次走两步
slow = slow.next # 每次走一步
if fast == slow: # 若有环, 两轮遍历后快慢指针必定相遇
return True
return False
法一改:受到法三启发,优化了循环条件,减少了不必要的语句。可见,良好的循环条件设计对代码的规范、简洁、高效等而言具有一定的重要性。但仍存在会破坏输入链表的缺点。空间复杂度 O(1),时间复杂度 O(n)。
2020/07/09 - 56.61% - 变换不大,但可见元素赋值替换的代价还是不小,相当于 用时间换空间
class Solution:
def hasCycle(self, head: ListNode) -> bool:
curr = head # 指向头部
while curr: # 遍历链表
if curr.val == "OK": # 若下一个节点元素为已遍历标记, 表示有环
return True
curr.val = "OK" # 否则, 令当前节点保存已遍历标记 "OK"
curr = curr.next # 指向下一个节点
return False # 若退出循环, 则存在节点引用为 None, 表示无环
官方实现与说明
法一:哈希表。
class Solution:
def hasCycle(self, head: ListNode) -> bool:
dic = {} # 像个人实现法二使用集合也可以, 但哈希表更好
curr = head
while curr:
if curr in dic:
return True
else:
dic[curr] = 1
curr = curr.next
return False
法二:双指针。
# 同个人实现法三
class Solution:
def hasCycle(self, head: ListNode) -> bool:
slow = fast = head # 快、慢指针起点相同
while fast and fast.next: # 重要循环条件
fast = fast.next.next # 每次走两步
slow = slow.next # 每次走一步
if fast == slow: # 若有环, 两轮遍历后快慢指针必定相遇
return True
return False