1,题目要求
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put.
get(key)
- Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value)
- Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.
Follow up:
Could you do both operations in O(1) time complexity?
Example:
LRUCache cache = new LRUCache( 2 /* capacity */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.put(4, 4); // evicts key 1
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4
设计并实现最近最少使用(LRU)缓存的数据结构。 它应该支持以下操作:get和put。
get(key)
- 如果密钥存在于缓存中,则获取密钥的值(将始终为正),否则返回-1。
put(key,value)
- 如果该键尚不存在,则设置或插入该值。 当缓存达到其容量时,它应该在插入新项之前使最近最少使用的项无效。
补充:
你可以在O(1)时间复杂度下进行这两项操作吗?
2,题目思路
对于这道题,题目是实现一个最近最少使用的缓存数据结构并实现get和put的操作。
题目的主要要求,就是实现一种数据操作,即我们可以根据key值,在对应的数据结构中读取;并且向其中添加新元素,如果该元素在数据结构中不存在,我们就直接加入。如果该元素已经在数据结构汇总出现了,我们就将其设置最近访问的元素。如果此时容器满了,就将最不常用的元素删除。
因此,这里涉及到的,就是一种访问频率问题。
直观的去想,我们可以为每个元素设置一个访问属性,比如,每次对容器的访问,都可以设置为一个时间片。如果当前元素没有被访问,就对访问频率属性进行一个累加,每次访问到的元素,就设置其访问频率归零。当我们需要删除元素时,就将当前访问频率值最大的元素删除。
这样虽然是可行的,但是每次的插入、删除、更新频率操作,都需要O(n)的时间复杂度,不满足O(1)的要求。
因此,在这个问题的实现上:
我们定义一个used,容器为list,用来保存当前访问的元素,其中,这个list的值为题目对应的key,而这些key在used中的位置(迭代指针所指的位置)作为频率的表示,front为最近访问的,back最久未访问的。
然后,我们定义一个cache,容器为一个unordered_map,用来保存输入的数据:其中,key是题目中对应的key,而value是一个pair,对于这个pair而言:
first是题目给定的value
second是一个迭代器(指针),表示这个元素在used中出现位置,方便我们后续的操作
最后,我们定义一个update函数,用来对新加入的元素或者重新访问的元素的频率属性进行更新。
其实,新加入的元素,就相当于最近访问的元素。
具体的实现和操作,见第三部分的代码及注释。
3,代码实现
static const auto s = []() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
return nullptr;
}();
class LRUCache {
public:
LRUCache(int capacity) {
_capacity = capacity;
}
int get(int key) {
auto it = cache.find(key);
if(it == cache.end())
return -1; //没有找到对应的元素
update(it); //更改为最近访问
return it->second.first; //返回key对应的value
}
void put(int key, int value) {
auto it = cache.find(key);
if(it != cache.end()) //key在cache中
update(it); //将其改为最近访问
else{
if(cache.size() == _capacity){
cache.erase(used.back()); //删除最后一个、即最不经常用的元素
used.pop_back();
}
used.push_front(key);
}
//因为每次加入元素,都会让该元素成为最近使用的
//因此,我们也就将对应的{value, 当前元素在used中位置(指针)}设置cache中key对应的value
cache[key] = {value, used.begin()};
}
private:
//用于表示used
typedef list<int> LI;
//用于表示cache中的value属性
//是cache与used之间联系的桥梁
//key:对应题中的value;
//value:key对应的数据在used中的存储位置
typedef pair<int, LI::iterator> PII;
//用于表示cache
//key:对应题中的key
//value:是一个pair,其中first是题中给出的value值,而second是个指针,表示其在used中出现的位置,方便进行更新操作
typedef unordered_map<int, PII> HIPII;
//定义一个cache,用于保存加入的数据
HIPII cache;
//保持一个list,用来表示元素的访问最近访问属性
//list的值为题中给定的key
//其中front为最近访问的,back是最不常访问的
LI used;
int _capacity;
//更改访问到的节点的“最近访问属性”
void update(HIPII::iterator it){
int key = it->first;
//删除当前元素在used中的占位信息
//需要注意的是,erase是通过指针进行删除的
//这也是我们保持iterator的原因
used.erase(it->second.second);
used.push_front(key); //将该元素设为新的最近使用元素
//以指针指向的位置作为cache与used之间的联系,
//方便在加入、重新访问该元素对“最近访问属性”进行修改
it->second.second = used.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);
*/