一、题目
设计并实现一个满足 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) 的平均时间复杂度运行。
二、思路
LRU 意为 Latest Recently Used Cache,即 最近最少使用,既然是 使用,即 get 和 put 这两种操作均会让被操作的页优先级提高,从而不被淘汰。
为了实现优先级提高,常用的数据结构(数组、链表、队列、堆、栈、树),我们只能选择链表,因为只需要线性结构所以不选择树,因为需要改变节点顺序所以选择链表。通过链表即可实现将最近时间内最少被使用的页淘汰的特性。
为了实现 O(1)的时间复杂度,而选择的链表却是 O(N),因此需要哈希表的辅助。
链表和哈希表这两者怎么结合呢?通过让哈希表的值是链表节点的地址来实现。
即下图结构:
以leetocde的输入数据为例,说明具体执行过程:
首先,LRUCache lRUCache = new LRUCache(2);
其次,lRUCache.put(1, 1); 在 HashTable 放入 k = 1,v = *list.Element;List 中向Head端添加一个 *list.Element,值为 entry{k=1,v=1}。此时 List 为 {1=1}
lRUCache.put(2, 2); 在 HashTable 放入 k = 2,v = *list.Element;List 中向Head端添加一个 *list.Element,值为 entry{k=2,v=2}。此时 List 为 {2=2},{1=1}
lRUCache.get(1); 通过 k = 1,在 HashTable 中找到 *list.Element,返回其值为 entry{k=1,v=1}; 并将该 *List.Element 调整到 List 的 Head 位置处。并调整 HashTable 指向新 *list.Element。此时 List 为 {1=1},{2=2}
lRUCache.put(3, 3); 此时 List 为 {3=3},{1=1},将 {2=2}逐出List,并将{2=2}逐出HashTable。
lRUCache.get(2); 通过 HashTable 未查到则返回 -1。List 未改变。
lRUCache.put(4, 4); List 变为 {4=4},{3=3}
lRUCache.get(1); 通过 HashTable 未查到则返回 -1。List 未改变。
lRUCache.get(3); 通过 HashTable 查到,List 调整为 {3=3},{4=4}
lRUCache.get(4); 通过 HashTable 查到,List 调整为 {4=4},{3=3}
三、编码
type entry struct {
k, v int}
type LRUCache struct {
cap int
mp map[int]*list.Element
lst *list.List
}
func Constructor(capacity int) LRUCache {
return LRUCache{
cap: capacity, mp: map[int]*list.Element{
}, lst: list.New()}
}
func (this *LRUCache) Get(key int) int {
e := this.mp[key]
if e == nil {
return -1
}
this.lst.MoveToFront(e)
return e.Value.(entry).v
}
func (this *LRUCache) Put(key int, value int) {
if e := this.mp[key]; e != nil {
e.Value = entry{
k: key, v: value}
this.lst.MoveToFront(e)
return
}
this.mp[key] = this.lst.PushFront(entry{
k: key, v: value})
if len(this.mp) > this.cap {
back := this.lst.Back()
e := this.lst.Remove(back)
delete(this.mp, e.(entry).k)
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* obj := Constructor(capacity);
* param_1 := obj.Get(key);
* obj.Put(key,value);
*/