在问 · 算法题解

146. LRU 缓存

中等链表有序字典力扣原题 ↗

题目描述

设计一个容量固定的缓存 LRUCache。缓存满了需要腾出位置时,删掉最久没有被访问的那个键。get 和 put 都算一次访问。

LRUCache(int capacity):用正整数 capacity 作为容量创建缓存。

int get(int key):键 key 在缓存中时返回对应的值,否则返回 -1。

void put(int key, int value):键已存在就把值更新为 value;不存在就加入这对键值。若加入后键的个数超过 capacity,移除最久未访问的键。

get 和 put 的平均时间复杂度都必须是 O(1)。输入以操作名列表和参数列表给出,输出是每次操作的返回值,没有返回值的操作记为 null。

示例 1
输入:操作 = ["LRUCache","put","put","get","put","get","put","get","get","get"]
参数 = [[2],[1,1],[2,2],[1],[3,3],[2],[4,4],[1],[3],[4]]
输出:[null,null,null,1,null,-1,null,-1,3,4]

解释:容量为 2。放入键 3 时键 2 最久未用,被移除;放入键 4 时键 1 被移除;最后键 3 和 4 仍可取到。

数据范围

  • 1 <= capacity <= 3000
  • 0 <= key <= 10000
  • 0 <= value <= 10^5
  • get 和 put 合计最多调用 2 * 10^5 次。

解题思路:有序字典

使用 OrderedDict 的哈希索引和双向有序结构;访问或写入后移到尾部,头部始终是最久未使用项。

为什么正确

键从头到尾按最近使用时间由旧到新排列,容量不超限。

复杂度分析

get、put 平均时间 O(1),空间 O(capacity)。

易错点

更新已有键也算一次使用;淘汰发生在最旧端。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

from collections import OrderedDict

class LRUCache:
    def __init__(self, capacity: int):
        self.capacity = capacity
        self.data = OrderedDict()

    def get(self, key: int) -> int:
        if key not in self.data:
            return -1
        self.data.move_to_end(key)
        return self.data[key]

    def put(self, key: int, value: int) -> None:
        self.data[key] = value
        self.data.move_to_end(key)
        if len(self.data) > self.capacity:
            self.data.popitem(last=False)

载入编辑器,自己跑一遍 →

其他解法

两种方法的结构相同,都是哈希表加双向链表;有序字典由库封装,代码最短;手写版本把链表操作写出来,更通用,也更能体现原理。

解法 2:哈希表 + 双向链表 时间 O(1),空间 O(capacity)

双向链表按使用时间从旧到新排列节点,头尾各放一个哨兵节点。哈希表把键映射到链表节点。get 和 put 命中时,把节点摘下再接到尾部。插入新键后若超出容量,就删除头哨兵后面的节点,并从哈希表中删掉它的键。

不依赖语言自带的有序字典,所有语言都能写出,也是面试要求手写的版本。代码更长,摘链和接链时要同时维护 prev 和 next 指针。

class DLinkedNode:
    def __init__(self, key=0, value=0):
        self.key = key
        self.value = value
        self.prev = None
        self.next = None


class LRUCache:
    def __init__(self, capacity: int):
        self.capacity = capacity
        self.cache = {}
        self.head = DLinkedNode()
        self.tail = DLinkedNode()
        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 _add_to_tail(self, node):
        node.prev = self.tail.prev
        node.next = self.tail
        self.tail.prev.next = node
        self.tail.prev = node

    def get(self, key: int) -> int:
        if key not in self.cache:
            return -1
        node = self.cache[key]
        self._remove(node)
        self._add_to_tail(node)
        return node.value

    def put(self, key: int, value: int) -> None:
        if key in self.cache:
            node = self.cache[key]
            node.value = value
            self._remove(node)
            self._add_to_tail(node)
            return
        node = DLinkedNode(key, value)
        self.cache[key] = node
        self._add_to_tail(node)
        if len(self.cache) > self.capacity:
            lru = self.head.next
            self._remove(lru)
            del self.cache[lru.key]

链表 专题的其他题目

查看 链表 全部题目 →