Python题解Leetcode Hot100之链表

1.相交链表

  • 解题思路
    快慢指针:分别求出两个链表的长度n1和n2,在长度较长的那个链表上,快指针先走n2 - n1,慢指针再出发,最后能相遇则链表相交
    时间复杂度O(m+n),空间复杂度O(1)
  • 代码
    # Definition for singly-linked list.
    # class ListNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.next = None
    
    class Solution:
        def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
            if not headA or not headB:
                return None
            l_a = 0
            l_b = 0
            node = headA
            while node:
                l_a += 1
                node = node.next
            node = headB
            while node:
                l_b += 1
                node = node.next
            node1 = headA 
            node2 = headB
            if l_b > l_a:
                l_a, l_b = l_b, l_a
                node1, node2, = node2, node1
            for _ in range(l_a - l_b):
                node1 = node1.next
            while node1 and node2:
                if node1 == node2:
                    return node1
                node1 = node1.next
                node2 = node2.next
            return None
    

2.翻转链表

  • 解题思路
    最基本的题目,一定要掌握。prev初始化成None,不需要dummy_head
    时间复杂度O(N),空间复杂度O(1)
  • 代码
    class Solution:
        def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
            if not head:
                return head
            # prev直接初始化成None就好
            prev = None
            cur = head
            nex = None
            while cur:
                nex = cur.next
                cur.next = prev
                prev = cur
                cur = nex
            return prev
    

3.回文链表

  • 解题思路
    查找链表的中间节点,然后翻转后半段,接着使用双指针比较判断即可
    时间复杂度O(N),空间复杂度O(1)
  • 代码
    # Definition for singly-linked list.
    # class ListNode:
    #     def __init__(self, val=0, next=None):
    #         self.val = val
    #         self.next = next
    class Solution:
        def isPalindrome(self, head: Optional[ListNode]) -> bool:
            def reverse(head):
                if not head:
                    return head
                prev = None
                cur = head
                nex = None
                while cur:
                    nex = cur.next
                    cur.next = prev
                    prev = cur
                    cur = nex
                return prev
            if not head:
                return False
            if not head.next:
                return True
            slow = head
            fast = head.next
            while fast and fast.next:
                slow = slow.next
                fast = fast.next.next
            
            head2 = reverse(slow.next)
            node_1 = head
            node_2 = head2
            
            while node_1 and node_2:
                if node_1.val != node_2.val:
                    return False
                node_1 = node_1.next
                node_2 = node_2.next
            return True
    

4. 环形链表

  • 题目描述
    判断链表是否有环
  • 解题思路
    快慢指针,快指针一次走一步,慢指针一次走两步,如果有环的话,他们一定在环中相遇。类比于操场跑圈的套圈,对于slow来说,fast是一个节点一个节点的靠近slow的
    时间复杂度:O(N),
    空间复杂度:O(1)。
  • 代码
    class Solution:
        def hasCycle(self, head: Optional[ListNode]) -> bool:
            if not head or not head.next:
                return False
            
            fast = slow = head
            while fast and fast.next:
                slow = slow.next
                fast = fast.next.next
                if slow == fast:
                    return True
            return False
    

5. 环形链表2

  • 题目描述
    如果链表有环需要返回入环的节点,无环返回None
  • 解题思路
    图片来自代码随想录;查找是否有环和上一题目一样,使用快慢指针,如果有环,那么他们一定在环内相遇。如下图所示,慢指针走过的路程是x + y,快指针走过的路程是 x + y + (y + z) * n,又因为快指针走的路程是慢指针的两倍,因此有x + y + (y + z) * n = 2* (x + y), 也就是(y + z) * n = x + y, 也就是(y+z)*(n-1) + z = x,那么如果两个节点分别从头结点和相遇节点出发,一定可以在入口节点处相遇;
    时间复杂度:O(N),
    空间复杂度:O(1)。在这里插入图片描述
  • 代码
    class Solution:
        def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
            if not head or not head.next:
                return None
            slow = fast = head
            has_cycle = False
            while fast and fast.next:
                slow = slow.next
                fast = fast.next.next
                if fast == slow:
                    has_cycle = True
                    break
            if not has_cycle:
                return None
            node1 = head
            node2 = slow
            while node1 != node2:
                node1 = node1.next
                node2 = node2.next
            return node1
    

6. 合并两个有序链表

  • 解题思路
    是链表排序和合并K个有序链表等题目要用的基本模块
    时间复杂度O(n+m), 空间复杂度O(n+m)
  • 代码
    # Definition for singly-linked list.
    # class ListNode:
    #     def __init__(self, val=0, next=None):
    #         self.val = val
    #         self.next = next
    class Solution:
        def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
            if not list1:
                return list2
            if not list2:
                return list1
            head = ListNode()
            cur = head
            node1 = list1
            node2 = list2
            while node1 and node2:
                if node1.val < node2.val:
                    cur.next = node1
                    node1 = node1.next
                else:
                    cur.next = node2
                    node2 = node2.next
                cur = cur.next
            if node1:
                cur.next = node1
            if node2:
                cur.next = node2
            return head.next
    

7. 两数相加

  • 题目描述
    在这里插入图片描述

  • 解题思路
    注意考虑进位、两个数字位数不同的情况
    时间复杂度:O(max(m,n)),其中 m 和 n 分别为两个链表的长度。我们要遍历两个链表的全部位置,而处理每个位置只需要 O(1) 的时间。
    空间复杂度:O(1)。注意返回值不计入空间复杂度。

  • 代码

    # Definition for singly-linked list.
    # class ListNode:
    #     def __init__(self, val=0, next=None):
    #         self.val = val
    #         self.next = next
    class Solution:
        def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
            if not l1:
                return l2
            if not l2:
                return l1
            prev = 0
            n1 = n2 = 0
            new_head = ListNode()
            node1 = l1
            node2 = l2
            cur = new_head
            while node1 or node2:
                n1 = node1.val if node1 else 0
                n2 = node2.val if node2 else 0
                s = n1 + n2 + prev
                node = ListNode(s % 10)
                prev = s // 10
                cur.next = node
                cur = cur.next
                if node1:
                    node1 = node1.next
                if node2:
                    node2 = node2.next
            if prev != 0:
                node = ListNode(prev)
                cur.next = node
            return new_head.next
    

8. 删除链表的倒数第N个节点

  • 解题思路
    快慢指针,快指针先走N,然后快慢一起走,当快指针走到末尾时,慢指针指向的就是要删除的节点
  • 代码
    # Definition for singly-linked list.
    # class ListNode:
    #     def __init__(self, val=0, next=None):
    #         self.val = val
    #         self.next = next
    class Solution:
        def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
            if not head:
                return None
            new_head = ListNode(0, head)
            prev = new_head
            slow = fast = head
            for i in range(n):
                if not fast:
                    raise ValueError("n must greter than length of list")
                fast = fast.next
            while fast:
                prev = slow
                slow = slow.next
                fast = fast.next
            prev.next = slow.next
            return new_head.next
    

9. 两两交换链表中的节点

  • 题目描述
    给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
  • 解题思路
    模拟两两交换的过程即可
  • 代码
    class Solution:
        def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
            if not head:
                return head
            new_head = ListNode(next=head)
            cur = head
            prev = new_head
            while cur and cur.next:
                next = cur.next
                cur.next = next.next
                next.next = cur
                prev.next = next
                prev = cur
                cur = prev.next
            return new_head.next
    

10. k个一组翻转链表

  • 题目描述
    给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

    k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

    你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

  • 解题思路
    将前k个节点切断成一个链表,进行翻转,并递归对剩下的链表进行‘k个一组翻转链表’操作,再将两个链表连起来,即可

  • 代码

    class Solution:
        def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
            def reverse(head):
                if not head:
                    return head
                prev = None
                cur = head
                nex = None
                while cur:
                    nex = cur.next
                    cur.next = prev
                    prev = cur
                    cur = nex
                return prev
            
            if not head:
                return head
            l = 0
            node = head
            while node:
                l += 1
                node = node.next
            if l < k:
                return head
            node = head
            for i in range(k - 1):
                node = node.next
            new_head = node.next
            node.next = None
            reverse_head = reverse(head)
            head.next = self.reverseKGroup(new_head, k)
            return reverse_head
    

11. 随机链表的复制

  • 解题思路
    第一遍循环,复制每个节点,并把他们通过next连接成一个普通的链表,同时构建哈希表,哈希表的key是旧的节点,value是复制的节点;
    第二遍循环,通过哈希表完成random的指定,注意random可能是空的
    时间复杂度O(N), 空间复杂度O(N)
  • 代码
    class Solution:
        def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
            if not head:
                return head
            dic = {}
            node = head
            new_head = Node(0)
            prev = new_head
            while node:
                new_node = Node(x=node.val)
                prev.next = new_node
                dic[node] = new_node
                prev = new_node
                node = node.next
            
            node = head
            while node:
                # 一定要注意原始节点的random是不是空的
                if node.random:
                    dic[node].random = dic[node.random]
                node = node.next
            return new_head.next
    

12. 排序链表

  • 题目描述

  • 解题思路
    解题思路:归并排序的思想,找到中间节点,然后分别对左右两边进行排序,最后合并左右两边的有序链表。
    这道题目的关键:1. 找到链表的中间节点:用快慢指针实现,慢指针一次走一步,快指针一次走两步,当快指针走到末尾时,慢指针指向的就是中间节点(不用求出链表长度再计算中间节点的位置);2.将链表从中间节点切断成两个链表;3. 合并两个有序链表。
    时间复杂度:O(nlogn),其中 n 是链表的长度。
    空间复杂度:O(logn),其中 n 是链表的长度。空间复杂度主要取决于递归调用的栈空间。

  • 代码

    # Definition for singly-linked list.
    # class ListNode:
    #     def __init__(self, val=0, next=None):
    #         self.val = val
    #         self.next = next
    class Solution:
       def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
           def merge(l1, l2):
               if not l1:
                   return l2
               if not l2:
                   return l1
               head = ListNode()
               cur = head
               node1 = l1
               node2 = l2
               while node1 and node2:
                   if node1.val < node2.val:
                       cur.next = node1
                       node1 = node1.next
                   else:
                       cur.next = node2
                       node2 = node2.next
                   cur = cur.next
               if node1:
                   cur.next = node1
               if node2:
                   cur.next = node2
               return head.next
           if not head or not head.next:
               return head
           # 找到中间节点的方法:快慢指针
           slow = head
           fast = head.next
           while fast and fast.next:
               slow = slow.next
               fast = fast.next.next
           node1 = head
           node2 = slow.next
            # 从中间断开链表
           slow.next = None
           head1 = self.sortList(node1)
           head2 = self.sortList(node2)
           return merge(head1, head2)
    

13. 合并k个升序链表

  • 题目描述
    给你一个链表数组,每个链表都已经按升序排列。
    请你将所有链表合并到一个升序链表中,返回合并后的链表。
  • 解题思路
    分治,通过递归两两合并,其中会用到合并两个有序链表这个函数,在上一个题目排序链表中也用到了,因此这个模块函数要掌握好;
  • 代码
    class Solution:
        def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
            def merge(l1, l2):
                if not l1:
                    return l2
                if not l2:
                    return l1
                head = ListNode()
                cur = head
                node1 = l1
                node2 = l2
                while node1 and node2:
                    if node1.val < node2.val:
                        cur.next = node1
                        node1 = node1.next
                    else:
                        cur.next = node2
                        node2 = node2.next
                    cur = cur.next
                if node1:
                    cur.next = node1
                if node2:
                    cur.next = node2
                return head.next
            n = len(lists)
            if n == 0:
                return None
            if len(lists) == 1:
                return lists[0]
            mid = n // 2
            head1 = self.mergeKLists(lists[: mid])
            head2 = self.mergeKLists(lists[mid :])
            return merge(head1, head2)
    

13 LRU

  • 题目描述
    请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
    实现 LRUCache 类:
    LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
    int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
    void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
    函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
  • 解题思路
    哈希表 + 双向链表。哈希表用于快速查找节点,双向链表用于存储使用情况,最近被使用的节点被放在双向链表的后端
    时间复杂度: O(1), 空间复杂度:O(capacity)。
    注意python的字典删除元素的方法是:pop(key[,default])
    删除字典给定键 key 所对应的值,返回值为被删除的值。key值必须给出。 否则,返回default值。
  • 代码
    class BiNode:
        def __init__(self, val=0, next=None, prev=None, key=None):
            self.val = val
            self.next = next
            self.prev = prev
            self.key = key
    
    
    class LRUCache:
    
        def __init__(self, n):
            self.n = n
            self.dic = {}
            self.head = BiNode()
            self.tail = BiNode()
            self.head.next = self.tail
            self.tail.prev = self.head
        
        def add_node_to_tail(self, node):
            self.tail.prev.next = node
            node.prev = self.tail.prev
            node.next = self.tail
            self.tail.prev = node
        
        def rm_node(self, node):
            prev = node.prev
            nex = node.next
            prev.next = nex
            nex.prev = prev
        
        def get(self, key):
            if key in self.dic:
                self.rm_node(self.dic[key])
                self.add_node_to_tail(self.dic[key])
                return self.dic[key].val
            else:
                return -1
        def put(self, key, value):
            if key in self.dic:
                self.dic[key].val = value
                self.rm_node(self.dic[key])
                self.add_node_to_tail(self.dic[key])
            else:
                if len(self.dic) == self.n:
                    to_delete = self.head.next
                    self.rm_node(to_delete)
                    self.dic.pop(to_delete.key)
                new_node = BiNode()
                new_node.val = value
                new_node.key = key
                self.dic[key] = new_node
                self.add_node_to_tail(new_node)
    
    
    # Your LRUCache object will be instantiated and called as such:
    # obj = LRUCache(capacity)
    # param_1 = obj.get(key)
    # obj.put(key,value)
    

总结

对于链表题目,主要的解题思路有:快慢指针、翻转链表(局部)、合并有序链表、查找中间位置的链表节点、将长链表分解切断成小的链表(分治)。
需要熟练掌握的模块:翻转链表、合并有序链表、查找中间位置的链表节点
查找中间位置的链表节点,使用快慢指针:

slow = head
fast = head.next
while fast and fast.next:
    slow = slow.next
    fast = fast.next.next
  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值