type
status
date
summary
tags
category
icon
password
featured
freq
difficulty
设计一个数据结构下的约束 最近使用(LRU)缓存 .
实现
LRUCache 类:LRUCache(int capacity):以 正整数 作为容量capacity初始化 LRU 缓存。
int get(int key): 如果key存在,返回的值 ,否则返回 -1.
void put(int key, int value):- 如果
key存在,更新key的值 。 - 否则,添加
key-value对。 - 如果
key的数量超过capacity, 删除最近最少使用的key。
函数
get 和 put 必须在每个运行在 O(1) 平均时间的复杂性。例:
解法一:直接用 OrderedDict
Python 自带的
collections.OrderedDict 本身就是“哈希表 + 双向链表”的组合:- 它内部已经按插入顺序维护键的顺序;
move_to_end(key):把某个 key 挪到末尾,相当于“标记为最近用过”;
popitem(last=False):弹出最前面的元素,相当于“淘汰最久未使用”的那个。
所以不用自己写双向链表,直接继承
OrderedDict,在 get 里查到就 move_to_end,在 put 里更新后 move_to_end,如果超容量就 popitem(last=False) 把最前面的丢掉。逻辑简单,但不太适合面试用,面试官通常想看手写双向链表以展示数据结构能力。
复杂度
- 时间复杂度:
get/put均为均摊 O(1);
- 空间复杂度:O(capacity)。
解法二:哈希表 + 双向链表(推荐)
我们想要的两个能力:
- O(1) 查找 key → value
用哈希表(dict)来做:
key -> 链表节点 Node。- 按“访问先后”维护一个队列,能快速移动一个节点到“最新”位置,快速淘汰“最旧”的那个
- 靠近链表头部的是最近访问的;
- 靠近尾部的是最久未访问的。
get(key):- 如果没找到,返回 -1。
- 找到了,把这个节点从原位置“摘”出来,挪到头部(标记为“刚用过”),然后返回值。
put(key, value):- 如果 key 已经存在:
- 更新节点 value;
- 把节点挪到头部。
- 如果 key 不存在:
- 创建新节点,挂到头部;
- 写进哈希表;
- 如果容量爆了,就从链表尾部删掉一个节点(最久未使用),顺便从哈希表删掉对应 key。
用双向链表:
get / put 的操作都要连带更新链表的顺序:
用两个dummy 头尾“伪节点”:
head:最前面一个伪头,不存真实数据;
tail:最后一个伪尾,也不存真实数据。
链表形态是:
head <-> ...真实节点... <-> tail。这样插入 / 删除节点就不用各种“如果是头节点怎么办”的 判断,所有节点之间的操作都可以写成统一的指针操作,不容易写出空指针 bug。
复杂度
- 时间复杂度:
get、put都是 O(1): - 哈希查找是 O(1);
- 双向链表增删节点也是 O(1)。
- 空间复杂度:O(capacity),哈希表和链表最多存 capacity 个节点。
解法三:把“删/插”封装成 Node 的方法
和解法二本质一模一样:哈希表 + 双链表 + dummy 头尾。
区别只是:我们把“从链表中删除自己”、“把自己插到某个节点后面”这类操作封装成
Node 的方法,比如:node.delete():把当前节点从链表中摘掉;
node.insert(prev_node):把当前节点插入到prev_node后面。
这种写法的好处是:
- LRUCache 核心逻辑读起来更像“操作一个对象”,而不是堆满指针细节;
- 指针操作都集中在 Node 类里,不到处复制粘贴,减少写错的概率。
复杂度
- 时间复杂度:
get/put依然是 O(1);
- 空间复杂度:O(capacity)。
- 作者:Fan Luo
- 链接:https://fanluo.me/article/leetcode-146-lru-缓存
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
上一篇
[Leetcode 143] 重排链表
下一篇
[Leetcode 160] 相交链表