【链表】(二) 双指针技巧

本文详细介绍了链表中的双指针技巧,包括环形链表、相交链表和删除链表倒数第N个节点的问题。讨论了不同解法,如哈希表、双指针法,并对比了它们的优劣。双指针法因其高效性在链表问题中被广泛使用。
摘要由CSDN通过智能技术生成

目录

一、链表中的双指针

二、环形链表

2.1 题目要求

2.2 解决过程

三、环形链表 II

3.1 题目要求

3.2 解决过程

四、相交链表

4.1 题目要求

4.2 解决过程

五、删除链表的倒数第N个节点

5.1 题目要求

5.2 解决过程

六、小结 - 链表中的双指针


一、链表中的双指针

参考文献: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
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值