146.LRU缓存
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity)以 正整数 作为容量capacity初始化 LRU 缓存int get(int key)如果关键字key存在于缓存中,则返回关键字的值,否则返回-1。void put(int key, int value)如果关键字key已经存在,则变更其数据值value;如果不存在,则向缓存中插入该组key-value。如果插入操作导致关键字数量超过capacity,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
示例:
1 | 输入 |
解题思路
这是一道经典的设计题,考察对LRU缓存的理解和实现。
核心思想
- 使用哈希表 + 双向链表的数据结构
- 哈希表提供O(1)的查找
- 双向链表维护访问顺序,支持O(1)的插入和删除
方法一:哈希表 + 双向链表(推荐)
时间复杂度 O(1)、空间复杂度 O(capacity)
1 | /** |
方法二:使用Map的内置特性
1 | var LRUCache = function(capacity) { |
方法三:使用Object模拟
1 | var LRUCache = function(capacity) { |
解题要点
数据结构选择:
- 哈希表:提供O(1)的查找
- 双向链表:维护访问顺序,支持O(1)的插入和删除
LRU策略:
- 最近使用的放在头部
- 最久未使用的在尾部
- 容量满时删除尾部节点
操作实现:
- get操作:查找并移动到头部
- put操作:更新或添加,并移动到头部
面试要点
- 高频考点:这道题是面试中的高频设计题
- 数据结构:考察对哈希表和链表的理解
- 时间复杂度:要求O(1)的操作复杂度
- 设计思维:考察系统设计能力
相关题目
- 460.LFU缓存 - 进阶版本,LFU缓存
- 432.全O(1)的数据结构 - 类似的设计题
- 380.O(1)时间插入、删除和获取随机元素 - 设计题
扩展思考
为什么使用双向链表?
- 支持O(1)的插入和删除
- 可以方便地移动到头部或删除尾部
时间复杂度分析
- get操作:O(1)
- put操作:O(1)
- 空间复杂度:O(capacity)
实际应用
- 浏览器缓存
- 数据库查询缓存
- 操作系统页面置换
变种问题
- LFU缓存(最不经常使用)
- 带过期时间的缓存
- 分布式缓存
优化思路
- 使用Map的内置特性简化实现
- 考虑内存使用和性能平衡
- 处理并发访问问题
最后更新: 2021-09-18