Hot100

4 阅读5分钟

146. LRU 缓存


# 定义双向链表节点类
# 每个节点需要存储:key、value、前驱指针、后继指针
class DLinkedNode:
    def __init__(self, key=0, value=0):
        self.key = key      # 必须存key!删除链表尾节点时,要靠它去哈希表删数据
        self.value = value  # 缓存存储的值
        self.prev = None    # 前驱节点指针
        self.next = None    # 后继节点指针

# LRU 缓存实现类
# 核心:哈希表 O(1) 查找 + 双向链表 O(1) 增删
class LRUCache:

    # 初始化构造方法
    def __init__(self, capacity: int):
        self.capacity = capacity  # 缓存最大容量(上限)
        self.size = 0             # 当前缓存里实际存了多少数据
        self.cache = {}           # 哈希表:key -> 双向链表节点,实现 O(1) 查找

        # 创建【虚拟头节点】和【虚拟尾节点】
        # 作用:不用判断空链表、头插、尾删的边界情况
        self.head = DLinkedNode()
        self.tail = DLinkedNode()
        
        # 初始化双向链表:head <-> tail
        self.head.next = self.tail
        self.tail.prev = self.head

    # 查询操作:根据 key 获取 value
    def get(self, key: int) -> int:
        # 如果 key 不在缓存里,返回 -1
        if key not in self.cache:
            return -1
        
        # key 存在:通过哈希表找到对应的节点
        node = self.cache[key]
        # 访问过了,把节点移到链表头部(标记为【最近使用】)
        self.moveToHead(node)
        # 返回节点的值
        return node.value

    # 插入/更新操作
    def put(self, key: int, value: int) -> None:
        if key in self.cache:
            # 情况1:key 已存在
            # 找到节点,更新 value
            node = self.cache[key]
            node.value = value
            # 访问过,移到头部
            self.moveToHead(node)
        else:
            # 情况2:key 不存在,新建节点
            new_node = DLinkedNode(key, value)
            # 加入哈希表
            self.cache[key] = new_node
            # 缓存数量 +1
            self.size += 1

            # 如果缓存满了,需要删除【最久未使用】的节点(链表尾部)
            if self.size > self.capacity:
                # 删除尾部节点
                tail_node = self.removeTail()
                # 同时在哈希表里删除这个 key
                del self.cache[tail_node.key]
                # 缓存数量 -1
                self.size -= 1
            
            # 把新节点加到链表头部
            self.addToHead(new_node)
    
    # 工具方法:把一个节点【添加到链表头部】
    def addToHead(self, node: DLinkedNode):
        # 新节点的前驱指向 head
        node.prev = self.head
        # 新节点的后继指向 head 原来的 next
        node.next = self.head.next
        
        # 原来的头节点的前驱,指向新节点
        self.head.next.prev = node
        # head 的 next 指向新节点
        self.head.next = node

    # 工具方法:从双向链表中【删除指定节点】
    def removeNode(self, node: DLinkedNode):
        # 拿到要删除节点的 前驱 和 后继
        prev_node = node.prev
        next_node = node.next

        # 直接跳过当前节点,让前后节点互相连接
        prev_node.next = next_node
        next_node.prev = prev_node
    
    # 工具方法:把节点【移动到头部】
    # 两步:先删除,再插入头部
    def moveToHead(self, node: DLinkedNode):
        self.removeNode(node)
        self.addToHead(node)
    
    # 工具方法:【删除尾部节点】(最久未使用)
    def removeTail(self) -> DLinkedNode:
        # 尾节点的前驱,就是真正的最后一个有效节点
        tail_node = self.tail.prev
        # 从链表中删掉它
        self.removeNode(tail_node)
        # 返回被删掉的节点(用于去哈希表删 key)
        return tail_node

# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)

23. 合并 K 个升序链表

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    # 合并两个链表
    def merge(self, h1: Optional[ListNode], h2: Optional[ListNode]) -> Optional[ListNode]:
        cur = dummy = ListNode(0)
        while h1 and h2:
            if h1.val < h2.val:
                cur.next = h1
                h1 = h1.next
            else:
                cur.next = h2
                h2 = h2.next
            cur = cur.next
        cur.next = h1 if h1 else h2
        return dummy.next

    def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head or not head.next:
            return head
        
        # 获取链表总长度
        n = 0
        cur = head
        while cur:
            n += 1
            cur = cur.next
        
        dummy = ListNode(0, head)
        intv = 1
        while intv < n:
            pre, cur = dummy ,dummy.next
            while cur:
                # 截取第一段有序链表
                h1 = cur
                for _ in range(intv - 1):
                    if cur and cur.next:
                        cur = cur.next
                    else:
                        break
                
                # 获取第二段有序链表,并断开第一段链表
                h2 = cur.next
                cur.next = None
                cur = h2
                for _ in range(intv - 1):
                    if cur and cur.next:
                        cur = cur.next
                    else:
                        break
                
                # 获取下一轮开始的指针,并断开第二段链表
                next_node = None
                if cur:
                    next_node = cur.next
                    cur.next = None
                
                # 合并两个链表
                merged = self.merge(h1, h2)
                pre.next = merged
                while pre.next:
                    pre = pre.next
                
                cur = next_node
            intv *= 2
        return dummy.next

138. 随机链表的复制

class Solution:
    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
        # 边界情况:如果原链表为空,直接返回空
        if not head:
            return None
        
        # 创建哈希表,用于存储 原节点 -> 新复制节点 的映射关系
        node_map = {}
        # 定义游标指针,从头节点开始遍历
        cur = head
        
        # 第一次遍历:创建所有新节点,只赋值val,建立原节点和新节点的映射
        while cur:
            # 根据当前原节点的值,创建一个新的复制节点
            new_node = Node(cur.val)
            # 把 原节点:新节点 存入字典,方便后续查找对应关系
            node_map[cur] = new_node
            # 游标向后移动,继续处理下一个原节点
            cur = cur.next
        
        # 第二次遍历:给新节点设置 next 和 random 指针
        cur = head  # 重置游标,回到链表头
        while cur:
            # 从字典中取出当前原节点对应的新节点
            new_node = node_map[cur]
            # 新节点的next = 原节点next对应的新节点(没有则为None)
            new_node.next = node_map.get(cur.next, None)
            # 新节点的random = 原节点random对应的新节点(没有则为None)
            new_node.random = node_map.get(cur.random, None)
            # 游标向后移动
            cur = cur.next
        
        # 返回复制链表的头节点(原头节点对应的新节点)
        return node_map[head]


class Solution:
    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
        # 边界情况:链表为空直接返回
        if not head:
            return None
        
        # 第一步:在每个原节点后面插入对应的复制节点
        cur = head
        while cur:
            # 创建当前节点的复制节点
            copy = Node(cur.val)
            # 复制节点的next指向原节点的next
            copy.next = cur.next
            # 原节点的next指向复制节点,完成插入
            cur.next = copy
            # 游标移动到下一个原节点(跳过刚插入的复制节点)
            cur = copy.next
        
        # 第二步:给所有复制节点设置 random 指针
        cur = head  # 重置游标
        while cur:
            # 如果原节点有random指向,那么复制节点的random = 原节点random的下一个节点(对应复制节点)
            if cur.random:
                cur.next.random = cur.random.next
            # 每次跳两步,只遍历原节点
            cur = cur.next.next
        
        # 第三步:将原链表和复制链表分离,得到最终的深拷贝链表
        cur = head
        copy_head = head.next  # 记录复制链表的头节点,最后要返回
        copy_cur = copy_head   # 复制链表的游标
        
        while cur:
            # 原节点的next恢复为原next(跳过复制节点)
            cur.next = copy_cur.next
            # 原链表游标后移
            cur = cur.next
            # 如果还有后续节点,复制链表游标继续后移
            if cur:
                copy_cur.next = cur.next
                copy_cur = copy_cur.next
        
        # 返回复制好的新链表头
        return copy_head