Algorithms & Data Structures : LeetCode 146. LRU Cache

吳建興
9 min readOct 31, 2021

--

題目:

這一題在一年前就已經寫出來了,但是一年前的 Code 使用最新的測資卻會 LTE , 於是一年後再來寫寫看這一題,希望這一年以來,我能有一些些的長進。

然而一年以後,仍舊無法把 get 以及 put 壓到 O(1),僅僅是從原本的 O(N log (capacity)) 優化到了 O(log(capacity)),看來還是太嫩了 (´・ω・`)

以前的解法:

時間複雜度:O(N * N log(capacity))
空間複雜度:O(capacity)

N 代表呼叫 get + put 數量的總數
capacity 就是參數的那個 capacity

使用 map 來儲存鍵值對,並用一個變數來記錄目前總共容納了多少個元素。當需要拋棄 "the least recently used key" 的時候,在 map 裡遍尋 timeStamp 最小的元素。

當執行 put 或是 get 某個鍵時,就更新 timeStamp。

class LRUCache {
public:

int global_timer = 0;
map<int, vector<int>> mp;

int max_size = 0;
int now_size = 0;

LRUCache(int capacity) {
max_size = capacity;
now_size = 0;
}

int get(int key) {
if(mp.find(key) == mp.end()) {
return -1;
} else {
global_timer++;
mp[key][1] = global_timer;
return mp[key][0];
}
return 0;
}

void put(int key, int value) {
global_timer++;

if(mp.find(key) != mp.end()){
mp[key][0] = value;
mp[key][1] = global_timer;
} else if(now_size <= max_size) {
now_size++;
mp[key] = vector<int>{value, global_timer};
} else {
int min = 1e9;
int min_key;

//找到 key 最小的元素
for(auto i = mp.begin();i != mp.end();i++) {
if(i->second[1] < min) {
min = i->second[1];
min_key = i->first;
}
}

mp.erase(min_key);
mp[key] = vector<int>{value, global_timer};
}
}
};

現在的解法:

時間複雜度:O(N log (capacity))
空間複雜度:O(capacity)

使用 map 來記錄 timeStamp 與 key 的對應關係。因為 map 本身是 binary search tree , 所以使用 O(log (capacity)) 的時間,可以找出 timeStamp 最小的 key 是哪一個。

因為當需要拋棄 “the least recently used key” 的時候,每次的 get 以及 put 都需要 O(log (capacity)) 所以總時間為 O(N log(capacity))

經過這一個改善,已經可以解決 TLE 的問題了。

class LRUCache {
public:

unordered_map<int, int> k2v;
unordered_map<int, int> k2t;
map<int, int> t2k;

int c;
int nowc;
int globalt;
LRUCache(int capacity) {
c = capacity;
nowc = 0;
globalt = 0;
}

int get(int key) {

if(k2v.find(key) == k2v.end()) {
return -1;
}

globalt++;

// erease old timeStamp
int tempT = k2t[key];
t2k.erase(tempT);

k2t[key] = globalt;
t2k[globalt] = key;

return k2v[key];
}

void put(int key, int value) {
globalt++;

if(k2v.find(key) != k2v.end()) {
int tempT = k2t[key];
t2k.erase(tempT);
k2t[key] = globalt;
t2k[globalt] = key;
k2v[key] = value;
} else {
if(nowc == c) {
// evict least recently used key
int leastT = t2k.begin()->first;
int leastK = t2k[leastT];
k2v.erase(leastK);
k2t.erase(leastK);
t2k.erase(leastT);
} else {
nowc++;
}
k2v[key] = value;
k2t[key] = globalt;
t2k[globalt] = key;
}
}
};

LeetCode 詳解:Hashmap + DoubleLinkedList

時間複雜度:O(N)
空間複雜度:O(capacity)

我不確定我這邊的實做是否與 LeetCode 的詳解相同,但總之這個優化可以確確實實地把 get 以及 put 操作的時間複雜度壓在 O(1) !

使用一個 hashMap 去紀錄 key <-> {value, address of Node in a doubly linked-list}。

每當一個 key 原本就存在,並被使用或更新時,就將它從 doubly linked list 裡刪除,並放在 doubly linked list 的最後面。

當一個 key 原本並不存在,就直接放在 doubly linked list 的最後面就好。

當想要拋棄 “the least recently used key” 的時候,就從 doubly linked-list 的最前端拿走那個節點就好了。

typedef struct Node Node;
struct Node{
int key;
int val;
Node *prev;
Node *next;
};
class LRUCache {
public:

int c;
int nowc;
Node *head;
Node *tail;
unordered_map<int, Node *> um;

LRUCache(int capacity) {
c = capacity;
nowc = 0;

head = new Node();
head->key = -1;
head->val = -1;
tail = new Node();
tail->key = -2;
head->val = -2;

head->next = tail;
head->prev = NULL;
tail->next = NULL;
tail->prev = head;
}

int get(int key) {
if(um.find(key) == um.end()) {
return -1;
}

// update doubly linked-list
Node *nown = um[key];
Node *prevn = nown->prev;
Node *nextn = nown->next;

// delete Node
prevn->next = nextn;
nextn->prev = prevn;

// insert Node
prevn = tail->prev;
nextn = tail;

prevn->next = nown;
nextn->prev = nown;
nown->prev = prevn;
nown->next = nextn;

return um[key]->val;
}

void put(int key, int value) {
if(um.find(key) == um.end()) {
if(nowc == c) {
// delete old Node
Node *nown = head->next;
Node *prevn = head;
Node *nextn = nown->next;

prevn->next = nextn;
nextn->prev = prevn;
um.erase(nown->key);
//delete(nown);
} else {
nowc++;
}
// insert new Node into doubly linked-list
Node *nown = new Node();
nown->val = value;
nown->key = key;

um[key] = nown;

//insert Node
Node *prevn = tail->prev;
Node *nextn = tail;

prevn->next = nown;
nextn->prev = nown;
nown->prev = prevn;
nown->next = nextn;

} else {
Node *nown = um[key];
Node *prevn = nown->prev;
Node *nextn = nown->next;

// update value
nown->val = value;

// delete Node
prevn->next = nextn;
nextn->prev = prevn;

// insert Node
prevn = tail->prev;
nextn = tail;

prevn->next = nown;
nextn->prev = nown;
nown->prev = prevn;
nown->next = nextn;
}
}
};

--

--

吳建興
吳建興

Written by 吳建興

I want to be a good programmer.(´・ω・`)

No responses yet