LC 146设计与位运算中等第 71 / 95 题

LRU 缓存

LRU Cache

设计哈希表双向链表
本机进度仅保存在当前浏览器

题目描述

请你设计并实现一个满足 LRU(最近最少使用)缓存约束的数据结构:LRUCache(int capacity) 以正整数容量初始化;get(key) 存在则返回值并使其成为最近使用,否则返回 -1;put(key, value) 存在则修改并置为最近使用,不存在则插入;容量超限时淘汰最久未使用的键。get 与 put 均要求 O(1) 平均时间。

示例:capacity = 2,依次 put(1,1)、put(2,2)、get(1)、put(3,3)(淘汰 2)、get(2) 返回 -1。

解题思路

  1. O(1) 读写 + O(1) 排序使用时间,只有「哈希表 + 双向链表」的组合能做到:哈希表定位节点,链表维护使用顺序。
  2. 双向链表的优势是 O(1) 摘除任意节点——把最近使用的移到头部,淘汰时删尾部。
  3. 用哑头哑尾节点消除对头尾的特判;Python 也可直接使用 OrderedDict,但手写更能体现原理。

参考实现

查看参考实现Python · 建议先自行作答
class LRUCache:
    class Node:
        __slots__ = ('key', 'val', 'prev', 'next')

        def __init__(self, key=0, val=0):
            self.key, self.val = key, val
            self.prev = self.next = None

    def __init__(self, capacity):
        self.cap = capacity
        self.map = {}  # key -> 节点
        self.head, self.tail = self.Node(), self.Node()
        self.head.next = self.tail
        self.tail.prev = self.head

    def _remove(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev

    def _move_to_front(self, node):
        # prev 为 None 表示新节点尚未入链,无需摘除
        if node.prev is not None:
            self._remove(node)
        node.next = self.head.next
        node.prev = self.head
        self.head.next.prev = node
        self.head.next = node

    def get(self, key):
        node = self.map.get(key)
        if not node:
            return -1
        self._move_to_front(node)
        return node.val

    def put(self, key, value):
        node = self.map.get(key)
        if node:
            node.val = value
            self._move_to_front(node)
            return
        if len(self.map) >= self.cap:
            # 淘汰链表尾部最久未使用节点
            lru = self.tail.prev
            self._remove(lru)
            del self.map[lru.key]
        node = self.Node(key, value)
        self.map[key] = node
        self._move_to_front(node)

复杂度与归属

时间复杂度O(1) 各操作
空间复杂度O(capacity)
所属分类设计与位运算
题源LeetCode 146

关联教程

返回题图鉴