Day5,Hot100(35/100)

链表

160. 相交链表

找到A和B链表相交的部分,并返回相交部分

方法一:使用set存储节点的地址
(1)先逐步存储链表A的节点(实际上就是存储节点地址,这样子在对比地址空间时,可以找到真正相交的地方,而不会受到其他值相同的节点的影响
(2)逐步遍历B,对比B中每个节点与set中的地址

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
        set_a = set()
        while headA:
            set_a.add(headA)  # 存的是节点, 而不是节点的值!!!
            headA = headA.next
        
        while headB:
            if headB in set_a:
                return headB
            headB = headB.next
            
        return None

方法二双指针
(1)由于链表的特性,两个链表相交部分一定是在链表的后半部分
(2)于是,将AB补全为等长,公共部分就会在补全后两个链表的同一个位置

首先是两个链表(约定,值相同代表同一节点,0 代表空节点)
A表:[1, 2, 3, 7, 8, 9] B表:[4, 5, 7, 8, 9]
连接两个链表(表与表之间用 0 隔开)
AB表:[1, 2, 3, 7, 8, 9, 0, 4, 5, 7, 8, 9, 0]
BA表:[4, 5, 7, 8, 9, 0, 1, 2, 3, 7, 8, 9, 0]
观察连接后的两个表,可以发现相交的部分整齐的排列在末尾, 只需要逐个比较这两张表的节点,就能找到相交的起始位置。

如果没有相交会如何?会陷入死循环吗? A表:[1, 2, 3] B表:[4, 5]
连接两个链表(表与表之间用 0 隔开) AB表:[1, 2, 3, 0, 4, 5, 0] BA表:[4, 5, 0, 1, 2, 3, 0]
最终AB和BA都为空,跳出循环

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
        A,B = headA,headB
        while A != B:
            A = A.next if A else headB
            B = B.next if B else headA
        return A

206. 反转链表

头插法
第一步:
最后一步:

class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        cur, pre = head, None
        while cur:
            tmp = cur.next
            cur.next = pre
            pre = cur
            cur = tmp
        return pre

234. 回文链表

方法一:暴力(遍历存储所有元素,在判断是否是回文)

class Solution:
    def isPalindrome(self, head: Optional[ListNode]) -> bool:
        l1 = []
        while head:
            l1.append(head.val)
            head = head.next
        l2 = l1[::-1]
        if l1 == l2:
            return True
        return False

方法二: 快慢指针 + 反转部分链表
(1)slow 和 quick 同时走,slow每次走一步,quick每次走两步,这样子当quick到尾巴时(null),slow刚好处于链表的中间。中间存在两种情况:
1、链表长度为偶数时(不加上null):实际上slow是停在中间两个节点的后一个,即后一个回文串的开头,此时可以直接比较。
2、链表长度为奇数时:slow会停在前后两个回文串的中间,需要向后移动一个,再开始比较。
(2)使用 cur 和 pre 指针,跟在slow指针后面反转前半部分链表
(3)从中间开始,向两端逐个比较元素是否相同

class Solution:
    def isPalindrome(self, head: Optional[ListNode]) -> bool:
        slow, quick = head, head
        cur, pre = head, None

        while quick and quick.next:
            quick = quick.next.next
            slow = slow.next

            tmp = cur.next
            cur.next = pre
            pre = cur
            cur = tmp

        # quick不为None,说明是奇数的情况,需要后移一位
        if quick:
            slow = slow.next
        
        while slow and pre and (slow.val == pre.val):
            slow = slow.next
            pre = pre.next
        
        # 如果是回文,则最终都是None
        if slow or pre:  
            return False
        return True

141. 环形链表

快慢指针(或用哈希,比较地址空间是否重复)

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        # 只有一个节点或为空时,不可能有环
        if not head or not head.next:
            return False

        slow, fast = head, head

        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if fast is slow:
                return True
        
        return False

142. 环形链表 II

题目:给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

快慢指针(或用哈希,返回地址空间重复的第一个位置)


a-c=(k-1)(b+c),表示slow和fast相遇后,slow距离入口还有c步。

当head和slow同时走c步,此时head与入口的距离为a-c,而slow正好在入口
根据a-c=(k-1)(b+c),head走完a-c步后一定会在入口与slow相遇

考虑最坏情况,slow进入循环时,fast在slow下一位。
此时根据相对速度(slow走一步,fast走两步 <—> slow不动,fast走一步)
fast要走(环长-1步)才能与slow相遇。

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        slow, fast = head, head
        while fast and fast.next:
            fast = fast.next.next
            slow = slow.next

            # slow,fast相遇后. slow 和 head一起走
            if slow == fast:
                while slow != head:
                    slow = slow.next
                    head = head.next
                return slow
        return None

21. 合并两个有序链表

方法一:使用新链表

class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        if not list1:
            return list2
        if not list2:
            return list1
        
        if list1.val < list2.val:
            head, new_ls = list1,list1
            list1 = list1.next
        else:
            head,new_ls = list2,list2
            list2 = list2.next

        while list1 and list2:
            if list1.val < list2.val:
                new_ls.next = list1
                list1 = list1.next
            else:
                new_ls.next = list2
                list2 = list2.next
            new_ls = new_ls.next
        
        new_ls.next = list1 if list1 else list2

        return head

方法二:不使用额外链表

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(-1)
        cur = head

        while list1 and list2:
            if list1.val < list2.val:
                cur.next = list1
                list1 = list1.next
            else:
                cur.next = list2
                list2 = list2.next
            cur = cur.next
        cur.next = list1 if list1 else list2

        return head.next

方法三:递归

class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        if not list1:
            return list2
        elif not list2:
            return list1
        elif list1.val < list2.val:
            list1.next = self.mergeTwoLists(list1.next, list2)
            return list1
        else:
            list2.next = self.mergeTwoLists(list1, list2.next)
            return list2

2.两数相加

由于链表是逆序的,所以只需顺序遍历,计算每一位是多少,是否有进位

class Solution:
    def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        l3 = ListNode(-1)
        head = l3

        ten = 0  # 是否进位
        while l1 or l2 or ten:
            ten += (l1.val if l1 else 0) + (l2.val if l2 else 0)
            l3.next = ListNode(ten % 10)
            ten //= 10
            l3 = l3.next
            if l1:
                l1 = l1.next
            if l2:
                l2 = l2.next
        return head.next

19. 删除链表的倒数第 N 个结点

题目:给你一个链表,删除链表的 倒数第n个结点,并且返回链表的头结点。

快慢指针
假设链表总长为 a,倒数第n个节点,就是第 a - n + 1 个节点
(倒数第1个节点是第a个节点,倒数第2个节点是a-1……)
(1)slow,fast,dummy的 next 都是 head。
(2)fast先走n步,此时 fast 距离最后一个节点的距离为 a - n
(3)slow和fast一起遍历到尾部(当fast.next为None是终止),此时slow走了a-n步,而我们的目标a-n+1就是slow.next
(4)删除slow.next

class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        # 创建dummy指向head,因为head有可能被删除
        slow = fast = dummy = ListNode(next=head)

        for _ in range(n):
            fast = fast.next
        
        while fast.next:
            fast = fast.next
            slow = slow.next  # slow 停止的位置就是删除的前一个结点
        
        slow.next = slow.next.next
        return dummy.next

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

方法一:迭代(交换tmp节点的后两个节点)



class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode()
        dummy.next = head
        cur = dummy

        while cur.next and cur.next.next:
            node1 = cur.next
            node2 = cur.next.next

            cur.next = node2
            node1.next = node2.next
            node2.next = node1

            cur = node1
        return dummy.next

方法二:递归
(1)每次交换相邻节点(node1和node2)
(2)交换后,node2成为新的头节点,node1.next,需要node3与node4交换后才可以确定

class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head or not head.next:
            return head
        
        node1 = head
        node2 = node1.next
        node3 = node2.next

        node2.next = node1
        node1.next = self.swapPairs(node3)

        return node2

25. K 个一组翻转链表

每k个,执行一次翻转部分链表

class Solution:
    # 翻转一段链表
    def reverse(self, head:ListNode, tail: ListNode):
        pre = tail.next
        cur = head
        while pre != tail:  # 说明当前段已经翻转完
            tmp = cur.next
            cur.next = pre
            pre = cur
            cur = tmp
        return tail,head  # 头尾互换

    def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
        dummy = ListNode()
        dummy.next = head
        pre = dummy

        while head:
            tail = pre
            for _ in range(k):
                tail = tail.next
                if not tail:
                    return dummy.next
            
            tail_next = tail.next
            new_head, new_tail = self.reverse(head, tail)
            # 接上翻转后的部分链表
            pre.next = new_head
            new_tail.next = tail_next

            pre = new_tail
            head = new_tail.next
        return dummy.next

138. 随机链表的复制

复制拥有random节点的链表

存在的问题,random节点指向位置可能还没复制到新的链表上
解决思路,使用链表

class Solution:
    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
        if not head: return
        d = dict()
        cur = head
        
        while cur:
            d[cur] = Node(cur.val)
            cur = cur.next
        
        cur = head
        while cur:
            d[cur].next = d.get(cur.next)
            d[cur].random = d.get(cur.random)
            cur = cur.next
        
        return d[head]

148. 排序链表

归并排序
(一)递归版
(1)快慢指针找中点,切分左右区间
(2)递归排序左右区间
(3)合并排序好的左右区间

class Solution:
    def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head or not head.next:
            return head

        # 1、通过快慢指针找链表中点(奇数,slow停在中点;偶数,slow停在左侧中点)
        slow, fast = head, head
        while fast.next and fast.next.next:
            fast = fast.next.next
            slow = slow.next
        mid, slow.next = slow.next, None  # 切断两个区间

        # 2、归并排序左右区间
        left, right = self.sortList(head), self.sortList(mid)

        new_head = cur = ListNode(0)

        # 3、合并左右区间 <==> 合并两个有序链表
        while left and right:
            if left.val < right.val:
                cur.next = left
                left = left.next
            else:
                cur.next = right
                right = right.next
            cur = cur.next
        cur.next = left if left else right

        return new_head.next

(二)递推版


23. 合并 K 个升序链表

方法一:最小堆
维护一个堆集合,里面包括当前值可能最小的节点,每次将堆中最小元素加入到链表中
(1)初始时,最小元素集合为所有链表的头节点
(2)之后每次pop堆顶,并加入最小节点的next(此时next是该剩余链表的头节点)
(3)最小堆就是,当前还未加入新链表的所有剩余链表的头节点

from heapq import heapify, heappush, heappop
# __lt__定义了对象小于(less than)的比较行为
# 当使用 < 运算符来比较两个对象时, Python解释器会自动调用这个方法
# 定义该方法, 使得堆可以根据节点的val来构建
ListNode.__lt__ = lambda a, b: a.val < b.val
class Solution:
    def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
        cur = dummy = ListNode()

        # 把所有的头节点都加入堆中
        # 因为所有都是有序的, 所以第一个最小值在这些头节点中
        h = [head for head in lists if head]
        heapify(h)

        while h:
            node = heappop(h)  # 当前最小节点
            cur.next = node
            cur = cur.next
            if node.next:
                heappush(h, node.next)
        return dummy.next

方法二:分治(类似归并排序,递归排序左右区间,最后融合)

class Solution:
    # 合并两个有序链表
    def merge_two(self, list1, list2):
        cur = dummy = ListNode()
        while list1 and list2:
            if list1.val < list2.val:
                cur.next = list1
                list1 = list1.next
            else:
                cur.next = list2
                list2 = list2.next
            cur = cur.next
        cur.next = list1 if list1 else list2
        return dummy.next

    def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
        m = len(lists)
        if m == 0:
            return None
        if m == 1:
            return lists[0]

        left = self.mergeKLists(lists[:m//2])
        right = self.mergeKLists(lists[m//2:])

        return self.merge_two(left, right)

146. LRU 缓存

LRU:最近最少使用。 当缓存满的时候,移除最近最少使用的元素。

  • get(k):如果cache中存在k,则返回k对应的v,并且将k移动到头部(表示最近使用了)
  • put(k):如果k已经在cache中,那就更新k对应的v,并将k移动到头部。
    – 如果k不在,先检查cache是否满了,满了先移除尾部元素,在把k加到头部

双向循环链表+哈希表
(1)双向循环:添加表头,删除表尾都是O(1)
(2)哈希表:快速找到指定节点

class Node:
	# __slots__限制类的实例只能有特定的属性
	# Python 默认是用 dict 存储属性的,每次用 . 访问属性都需要查字典。
	# 如果声明 __slots__ 就不会创建字典,而是改用指针偏移量直接拿到属性对象。
	# 所以既节省了内存(没有字典)又节省了时间(省去查字典的过程)。
    __slots__ = 'pre', 'next', 'key', 'value'

    def __init__(self, key=0, value=0):
        self.key = key
        self.value = value

class LRUCache:
    def __init__(self, capacity: int):
        self.capacity =capacity
        self.dummy = Node()
        self.dummy.pre = self.dummy
        self.dummy.next = self.dummy
        self.key2node = dict()

    def get_node(self, key: int) -> Optional[Node]:
        if key not in self.key2node:
            return None
        node = self.key2node[key]
        # 从原位置移动到头部
        self.remove(node)
        self.push_front(node)
        return node

    def get(self, key: int) -> int:
        node = self.get_node(key)
        return node.value if node else -1

    def put(self, key: int, value: int) -> None:
        node = self.get_node(key)
        # key存在cache中,更新val
        if node:
            node.value = value
            return
        # key不在cache中, 添加到cache头部
        self.key2node[key] = node = Node(key, value)
        self.push_front(node)
        # cache满了
        if len(self.key2node) > self.capacity:
            back_node = self.dummy.pre  # 获取最少使用的节点
            del self.key2node[back_node.key]
            self.remove(back_node)
    
    def remove(self, x:Node) -> None:
        x.pre.next = x.next
        x.next.pre = x.pre
    
    def push_front(self, x:Node) -> None:
        x.pre = self.dummy
        x.next = self.dummy.next
        self.dummy.next.pre = x
        self.dummy.next = x
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值