运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?
实例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
LRU(Least Recently Used),最近最少使用算法,是一种常用的页面置换算法,用来最大化页面命中率。选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间t,当必须淘汰一个页面时,选择现有页面中t值最大的,即最近最少使用的页面予以淘汰。(来自百度百科)
题目要求实现LRU缓存机制,需要在O(1)时间内完成:
- 获取键/检查键是否存在;
- 设置键;
- 删除最先插入的键;
前两个操作可以使用标准的哈希表在O(1)时间内完成。
LRU算法实际上是让你设计数据结构:首先要接收一个capacity参数作为缓存的最大容量,然后实现两个API,一个是**put(key, val)方法存入键值对,另一个是get(key)**方法获取key对应的val,如果key不存在则返回-1。并且get和put方法都应该是O(1)时间复杂度
方法一:有序字典(Python OrderedDict)
使用Python中的OrderedDict(有序字典)来实现。
from collections import OrderedDict
class LRUCache(OrderedDict):
def __init__(self, capacity: int):
self.capacity = capacity
def get(self, key: int) -> int:
if key not in self:
return -1
self.move_to_end(key) # 把当前的键值移动到最后端
return self[key]
def put(self, key: int, value: int) -> None:
if key in self:
self.move_to_end(key)
self[key] = value
if len(self) > self.capacity:
self.popitem(last=False) # 如果超出了容量,从最前端弹出元素(最久未使用的);
方法二:哈希链表(C++)
为了满足插入块,查找快,删除快,有顺序之分的特点,使用哈希表和双向链表组合而成的哈希链表。哈希表满足了查找、插入、删除快的特性,而双向链表满足了有顺序的特点。链表的删除需要得到前一结点指向它的指针,双向链表可以快速得到这个指针,保证了删除操作也可以在O(1)时间复杂度内完成。因为删除时要同时删除key-value键值对,所以链表的结点一定要同时保存key值和val值,否则在删除时无法通过val值找到与之对应的key值。
/*
哈希表加双向链表
*/
class LRUCache {
int cap; // 容量
// 双向链表,储存(key, value)键值对;
list<pair<int, int> > cache;
// 哈希表:key映射到(key,value)在cache中的位置;
unordered_map<int, list<pair<int, int> >::iterator > hashmap;
public:
LRUCache(int capacity) {
// 设定容量
this->cap = capacity;
}
int get(int key) {
auto it = hashmap.find(key); //it是一个迭代器;
// 访问的key不存在
if (it == hashmap.end()) return -1;
// key存在,把(k,v)换到队头(使用先删除再插入的方式)
pair<int, int> kv = *hashmap[key];
cache.erase(hashmap[key]);
cache.push_front(kv);
// 更新(key,value)在cache中的位置;
hashmap[key] = cache.begin();
return kv.second; // 返回value值;
}
void put(int key, int value) {
// 要首先判断key是否已经存在
auto it = hashmap.find(key);
if (it == hashmap.end()){
// 当前key在cache中不存在,需要进行插入
// 判断cache是否已满
if (cache.size() == this->cap) {
// cache已满,删除尾部的键值对腾位置
// cache和map中的数据都要删除
// 删除cache末尾的键值对
auto lastPair = cache.back();
int lastKey = lastPair.first;
hashmap.erase(lastKey);
cache.pop_back();
}
// cache没满,可以直接添加
cache.push_front(make_pair(key, value));
hashmap[key] = cache.begin();
}
else {
// key存在,更改value并换到队头
cache.erase(hashmap[key]);
cache.push_front(make_pair(key, value));
hashmap[key] = cache.begin();
}
}
};
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/