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)
的平均时间复杂度运行。
示例:
输入
["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]
解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4
提示:
1 <= capacity <= 3000 0 <= key <= 10000 0 <= value <= 105 最多调用 2 * 105 次 get 和 put
解题思路
实现功能
主要功能是 Get 和 Put,先分析一下这两个方法需要实现什么功能
Get
- 获取缓存值
- 提高key的优先级
Put
- key 已存在
- 设置缓存
- 设置当前key 的优先级为最高
- key 不存在
- 设置缓存
- 设置当前key 的优先级为最高
- 检查缓存容量
数据结构
首先是缓存内容,便于存取,使用map 最合适
其次是优先级,他需要需要支持
- 将某个key的优先级提到最高
- 删除优先级最低的key
这里就考虑到了 切片、链表
- 切片:删除key 比较方便。但是定位key来提升优先级需要 O(n) 的复杂度
- 链表:删除key 需要知道表尾,用双向链表。提升优先级将对应的节点提到表头即可。双向链表完成这两个能力的成本更低。
代码
type Node struct {
key, val int
pre, next *Node
}
type LRUCache struct {
m map[int]*Node // 缓存映射
head, tail *Node // 双向链表,维护优先级,head 指向优先级最高
cap int // 最大容量
size int // 当前缓存数量
}
func Constructor(capacity int) LRUCache {
l := LRUCache{
m: make(map[int]*Node, capacity),
head: &Node{},
tail: &Node{},
cap: capacity,
}
l.head.next = l.tail
l.tail.pre = l.head
return l
}
/*
*
Get 获取缓存
1. 获取缓存值
2. 提高当前key 的优先级
*/
func (this *LRUCache) Get(key int) int {
node, ok := this.m[key]
if ok {
this.moveToHead(node)
return node.val
}
return -1
}
/*
*
Put 设置缓存
两种情况:
case1:key 已存在
1. 设置缓存
2. 提高当前key 的优先级
case2: key 不存在
1. 设置缓存
2. 在表头插入key
3. 检查缓存容量
*/
func (this *LRUCache) Put(key int, value int) {
node, ok := this.m[key]
if ok {
node.val = value
this.moveToHead(node)
} else {
node = &Node{
key: key,
val: value,
}
this.m[key] = node
this.addToHead(node)
this.size++
}
for this.size > this.cap {
tail := this.removeTail()
delete(this.m, tail.key)
this.size--
}
}
func (this *LRUCache) addToHead(node *Node) {
node.pre = this.head
node.next = this.head.next
this.head.next.pre = node
this.head.next = node
}
func (this *LRUCache) removeNode(node *Node) {
node.pre.next = node.next
node.next.pre = node.pre
}
func (this *LRUCache) moveToHead(node *Node) {
this.removeNode(node)
this.addToHead(node)
}
func (this *LRUCache) removeTail() *Node {
node := this.tail.pre
this.removeNode(node)
return node
}