思路:
题目的主要信息:
- 实现LRU缓存的模拟结构,包括加入函数set,访问函数get
- 结构有长度限制,加入新数时,超出长度则需要删除最不常访问的,其中set与get都访问
- 两个函数都是O(1)
方法一:构建双向链表
插入与访问值都是O(1),没有任何一种数据结构可以做到。 于是我们可以想到数据结构的组合。访问O(1)很容易想到了哈希表,插入O(1)有很多,但是如果访问到了再插入,且超出长度要在O(1)之内删除,我们可以想到用链表。因为要在O(1)之内删除最不常访问的,所以是双向链表。于是我们的方法就是哈希表+双向链表。
具体做法:
用哈希表存储链表结点和key值,能够做到O(1)访问链表任意结点,每次调用函数后将该结点放到链表最前方表示权重最大,最常访问,每次删除链表最后一个结点。要实现这个操作,我们需要的是有头结点和尾结点的双向链表。
class Solution {
public:
struct node {//设置双向链表结构
node* next;
node* pre;
int key;
int val;
node(int k, int v) : key(k), val(v), next(NULL), pre(NULL) {}//可以直接输入数字初始化
};
node* head = new node(-1, -1);//设置一个头
node* tail = new node(-1, -1);//设置一个尾
int length = 0;//记录链表中有效结点(除去头尾)的数量
map<int, node* > mp; //哈希表
void update(node* p) { //每次访问后,更新优先度,即将访问的节点放在第一个位置
//将p节点从原位置删除
node* q = p->pre;
q->next = p->next;
p->next->pre = q;
//将p节点插入到第一个位置
p->next = head->next;
head->next->pre = p;
head->next = p;
p->pre = head;
}
void set(int key, int val, int k) { //插入数据
if (mp.count(key)) //插入的数据已经存在,更新p节点的位置
update(mp[key]);
else { //否则加入数据
node* p = new node(key, val); //创建新节点加入哈希表
mp[key] = p;
length++;
//将p节点插入到第一个位置
p->next = head->next;
p->next->pre = p;
head->next = p;
p->pre = head;
if (length > k) { //超出LRU缓存空间
node* q = tail->pre->pre;
node* t = q->next;
q->next = tail;
tail->pre = q;
mp.erase(t->key);//删除map里面的数据
delete t;
}
}
}
int get(int key) { //访问数据
if (mp.count(key)) { //哈希表找到数据更新节点,并返回
node* p = mp[key];
update(p);
return p->val;
}
else {//返回-1
node* p = new node(-1, -1);
return p->val;
}
}
vector<int> LRU(vector<vector<int> >& operators, int k) {
head->next = tail;
tail->next = head; //先将链表首位相接,便于插入与删除
vector<int> res; //记录输出
for (int i = 0; i < operators.size(); i++) {
if (operators[i][0] == 1)
set(operators[i][1], operators[i][2], k);
else if (operators[i][0] == 2) {
res.push_back(get(operators[i][1]));
}
}
return res;
}
};
复杂度分析:
- 时间复杂度:O(nlgn),map为哈希表
- 空间复杂度:O(k),链表和哈希表都是O(k)的辅助空间
方法二:利用LST中的list类
具体方法:
用list代替新建链表,每次访问过后将其取出加到链表最前方,要去掉一位时,直接去掉最后一位。
class Solution {
public:
list <pair<int, int> > plist; //用于list列表对模拟双向链表
unordered_map<int, list<pair<int, int> >::iterator> mp; //用于set 方法
int capacity; //每次存放的最大容量
void set(int key, int value)
{
auto iter = mp.find(key);
if(iter != mp.end()){//找到将其取出放到第一位
plist.erase(mp[key]);
plist.push_back({key, value});
mp[key] = plist.begin();
}else{
if(capacity == plist.size()){ //容量满则去掉最后一位
mp.erase(plist.back().first);
plist.pop_back();
}
plist.push_front({key, value});
mp[key] = plist.begin();
}
}
int get(int key)//获取那个经常使用的键值对
{
auto iter = mp.find(key);
if(iter != mp.end()){ //找到将其取出放到第一位
plist.erase(mp[key]);
plist.push_front({key,iter->second->second});
mp[key]=plist.begin();
return iter->second->second; //返回值
}else
return -1;
}
vector<int> LRU(vector<vector<int> >& operators, int k) {
vector<int> res;
if(k == 0)
return res;
capacity = k;
int n = operators.size();
for(int i = 0; i < n; i++){
if(operators[i][0] == 1){
set(operators[i][1], operators[i][2]);
}
else if(operators[i][0] == 2){
res.push_back(get(operators[i][1]));
}
}
return res;
}
};
复杂度分析:
- 时间复杂度:O(n),所有函数访问都是O(1),n取决于operators数组的大小
- 空间复杂度:O(k),链表和哈希表都是O(k)的辅助空间