题意整理
设计LRU(最近最少使用)缓存结构,结构有两个功能:get和set,且大小固定为K,set操作超出长度K时删除最少使用的内容,get或set操作的内容立马刷新为最常使用,要求set和get操作的时间复杂度为。
思路
要求get和set操作都为复杂度:
- get操作为
,首先想到的是字典,即哈希表,哈希表能够在
内实现查询
- set操作为
,set时如果key不在缓存中,那直接进行插入,如果已经在缓存中,则需要修改key的优先级,这这涉及到key在缓存中的位置的移动,要移动元素首先需要找到元素的位置,然后进行元素的挪移,要保证
的复杂度,链表是比较适合的结构。另外当缓存已满时,需要删除最不常用的元素,使用双向链表能够快速的删除末位元素。
下面实现了两种方法,分别使用自己实现的双向链表和STL中实现的list与哈希表一起实现LRU缓存结构。自己实现的双向链表没有实现的set和get功能,受限于需要查找元素,复杂度在
。
方法一 自己实现双向链表与哈希表进行组合
自己实现了一个双向链表类,data域存键值对的key值,set或get操作时,都用双向链表维护key值的优先级,被get的key从原处挪到链表头部,set时直接插入到头部,如果长度超过K值则删除尾部元素。另外用一个哈希表进行键值对的保存。
示例
输入:[[1,1,1],[1,2,2],[1,3,2],[2,1],[1,4,4],[2,2]],3
代码
class BiDirectionLinkList { private: struct node {//双向链表结点 node* pre; node* next; int key; node() : key(-1), pre(NULL), next(NULL) {}; }; node* head; node* tail; int length; public: BiDirectionLinkList() {//构造函数 head = new node;//头结点 tail = new node;//尾节点 head->next = tail; tail->pre = head; length = 0; } void insert(int k) {//插入新元素,头插法 node* temp = new node; temp->key = k; temp->pre = head; temp->next = head->next; head->next->pre = temp; head->next = temp; length++; } int remove() {//删除链表最后一个元素,并返回该值 node* temp = tail->pre; temp->pre->next = tail; tail->pre = temp->pre; int k = temp->key; length--; delete temp; return k; } void update(int k) {//访问元素后将该元素位置更新到头结点后面 //找到k所在结点p node* p = head->next; while (p->key != k) { p = p->next; } //将p结点从原处删除 node* q = p->pre; p->next->pre = q; q->next = p->next; //将p结点插入头结点 p->pre = head; p->next = head->next; head->next->pre = p; head->next = p; } void travel() { node* p = head->next; while (p != tail) cout << p->key << " "; } int size() { return length; } }; class Solution { public: /** * lru design * @param operators int整型vector<vector<>> the ops * @param k int整型 the k * @return int整型vector */ BiDirectionLinkList linkList; //双向链表 map<int, int > hashMap; //哈希表,存储键值对 void set(int key, int value, int k) { if (hashMap.count(key)) {//插入的key已经存在,更新key的位置及对应的value linkList.update(key); hashMap[key] = value; } else {//数据不在map中,插入数据 if (linkList.size() == k) {//如果linkList长度已满k,删除最后一个元素 int temp = linkList.remove(); hashMap.erase(temp); } linkList.insert(key); hashMap[key] = value; } } int get(int key) { if (hashMap.count(key)) {//如果存在key,取值,更新缓存 linkList.update(key); return hashMap[key]; } else return -1; } vector<int> LRU(vector<vector<int> >& operators, int k) { // write code here vector<int> res; //存储结果 int n = operators.size(); for (int i = 0; i < n; i++) { vector<int> temp = operators[i]; if (temp.size() == 2)//get 操作 res.push_back(get(operators[i][1])); else// set操作 set(operators[i][1], operators[i][2], k); } return res; } };
复杂度分析
时间复杂度:,由于自己的实现的双链表,在对元素位置进行更新时,需要遍历链表以寻找被更新的元素,查找的复杂度为
,因此set最坏的情况下复杂度为
,get也为
,总的复杂度为
。
空间复杂度:,双链表和哈希表消耗空间都为
。
方法二 使用STL的list和哈希进行组合
STL的list也是双链表,能够进行快速插入与删除,但利用其迭代器的功能,在链表中存储键值对,在哈希中直接存储键值对在链表中的位置,这样在get时可以直接获得键值对在链表中的位置,进而取值,并直接更替元素的位置,set时同理。
代码
class Solution { public: /** * lru design * @param operators int整型vector<vector<>> the ops * @param k int整型 the k * @return int整型vector */ list<pair<int, int>> linkList; //双向链表 map<int, list<pair<int, int>>::iterator> hashMap; //哈希表,存储键值对 void set(int key, int value, int k) { if (hashMap.count(key)) {//插入的key已经存在,更新key的位置及对应的value linkList.erase(hashMap[key]); linkList.push_front({key,value}); hashMap[key] = linkList.begin(); } else {//数据不在map中,插入数据 if (linkList.size() == k) {//如果linkList长度已满k,删除最后一个元素 auto temp = linkList.back(); linkList.pop_back(); hashMap.erase(temp.first); } linkList.push_front({key, value}); hashMap[key] = linkList.begin(); } } int get(int key) { if (hashMap.count(key)) {//如果存在key,取值,更新缓存 auto temp = hashMap[key]; linkList.erase(hashMap[key]); linkList.push_front({key,temp->second}); hashMap[key] = linkList.begin(); return temp->second; } else return -1; } vector<int> LRU(vector<vector<int> >& operators, int k) { // write code here vector<int> res; //存储结果 int n = operators.size(); for (int i = 0; i < n; i++) { vector<int> temp = operators[i]; if (temp.size() == 2)//get 操作 res.push_back(get(operators[i][1])); else// set操作 set(operators[i][1], operators[i][2], k); } return res; } };
复杂度分析
时间复杂度:获得答案的复杂度为,由于不需要进行查找,set和get操作复杂度都为
。
空间复杂度:,list和哈希表消耗空间都为
。