本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
请你设计并实现一个满足 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 <= 30000 <= key <= 100000 <= value <= 105- 最多调用
2 * 105次get和put
思路:
-
参考:力扣
-
更多lru相关知识可学习大佬文章:其实吧,LRU也就那么回事。
-
借助hash链表(hashmap + 双向链表)这一组合的数据结构
-
Put() 操作中:
- 若key已存在:更新值并将该节点移动到最前
- 若key不存在:判断链表容量是否已满,若满则移除链表尾元素后再加入新节点到链表,未满则直接加入
-
Get() 操作中:
- 若已存在:直接返回元素的val值,并把该元素移除并重新加入到首位置上去(链表头部表示近期访问过的节点元素)
- 若不存在:返回-1
-
注意:若元素被get()或put()操作都算被访问过,需要把该元素移除并重新加入到首位置上去
-
其中 AddNode() 方法将某节点元素重新加入到首位置,其中双向链表的指针操作较多,可参照图示:
编辑
时间复杂度: get()、put()方法,由于是hash链表的数据结构,通过空间换时间的方式,均为O(1)
空间复杂度: get()、put()方法,由于是hash链表的数据结构,通过空间换时间的方式,均为O(1)
type LinkNode struct {
key, val int
pre, next *LinkNode
}
// 其中head和tail都为虚拟节点,不存储实际数据,仅用于标记双向链表首尾位置
type LRUCache struct {
m map[int]*LinkNode
cap int
head, tail *LinkNode
}
func Constructor(capacity int) LRUCache {
head := &LinkNode{0, 0, nil, nil}
tail := &LinkNode{0, 0, nil, nil}
head.next = tail
tail.pre = head
return LRUCache{make(map[int]*LinkNode), capacity, head, tail}
}
func (this *LRUCache) Get(key int) int {
cache := this.m
if v, exist := cache[key]; exist {
this.MoveToHead(v)
return v.val
} else {
return -1
}
}
func (this *LRUCache) Put(key int, value int) {
tail := this.tail
cache := this.m
if v, exist := cache[key]; exist {
// 若存在:更新值并将该节点移动到最前
v.val = value
this.MoveToHead(v)
} else {
// 若不存在:判断链表容量是否已满,若满则移除链表尾元素后再加入新节点到链表,未满则直接加入
v := &LinkNode{key, value, nil, nil}
// 超出容量cap,删除最后元素
if len(cache) == this.cap {
delete(cache, tail.pre.key)
// tail是虚拟的尾节点,移除 tail.pre 等同于下面两句
this.RemoveNode(tail.pre)
// tail.pre.pre.next = tail
// tail.pre = tail.pre.pre
}
this.AddNode(v)
cache[key] = v
}
}
// 以下为内部公共函数
// 访问过某节点元素,则将该节点元素从当前位置移除并重新加入到首位置
func (this *LRUCache) MoveToHead(node *LinkNode) {
this.RemoveNode(node)
this.AddNode(node)
}
// 移除某节点元素
func (this *LRUCache) RemoveNode(node *LinkNode) {
node.pre.next = node.next
node.next.pre = node.pre
}
// 将某节点元素重新加入到首位置
func (this *LRUCache) AddNode(node *LinkNode) {
head := this.head
node.next = head.next
head.next.pre = node
node.pre = head
head.next = node
}