题目来自LeetCode:链接
题目描述
设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。
它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
示例:
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
思路
首先题目要求缓存从键映射到值,很容易想到用unordered_map,底层哈希表,查找效率高。
其次当容量满了之后需要删掉最近最少使用的项目,最直接的思路就是每次用完一个项目之后都将其移至末尾表示最新用过,而头部的项目就代表是“最近最少使用的”。为此我们需要使用一种头尾移动元素方便的数据结构,很容易想到使用双向链表,直接用STL库的list就行。
因此我们主要维护两种数据结构:哈希表(unordered_map)和双向链表(list),其中哈希表用于快速查找一个key是否已存在,查找效率O(1),双向链表用于快速找出最近最少使用的项目(即链表开头元素),查找效率O(1)。
具体实现的时候,当我们要将一个key对应的结点移到末尾表示最新用过时,为了快速找到该结点所处的位置,我们需要进行一点灵活处理:直接在unorder_map的value中保存list的迭代器,这样子我们找到key的同时也能得到对应结点的迭代器,时间复杂度O(1)。
同时需要注意一点,当我们要删掉最近最少用到使用的结点,即删掉链表头部结点时,不能忘了还要删掉哈希表中对应的键值对,这个时候我们就需要通过链表结点找到哈希表中对应的键值对了,如果我们的链表结点不保存其对应的key,那么每次找键值对都要遍历哈希表来找,效率很低。
因此我们还需要在链表结点中保存其对应的key,从而让通过链表结点找键值对的效率变为O(1),具体实现就是将链表结点设为pair<int,int>,其中第一个int记录对应的key,第二个结点记录保存的value数据。
代码如下:
class LRUCache {
public:
int capacity;
unordered_map<int,list<pair<int,int>>::iterator> map;
list<pair<int,int>> list; //pair.first记录该结点对应的key,pair.second记录value
LRUCache(int capacity) {
this->capacity=capacity;
}
int get(int key) {
//找到了key,要将其移至末尾表示最新访问过
if(map.find(key)!=map.end()){
int value=(*map[key]).second;
list.erase(map[key]);
list