A cache keeps a few recent results close at hand so they are quick to read again. It can only hold a limited number of entries, so when it is full it must throw one away. An LRU (Least Recently Used) cache throws away the entry that was used longest ago. Build a class LRUCache that stores integer keys with integer values.
The class keeps: int capacity, an unordered_map<int, int> called map (key to value) and a vector<int> called order (the keys, least recently used first).
Create it: LRUCache c(capacity) makes an empty cache that holds at most capacity entries.
Methods and rules:
int get(int key)returns the value stored forkey. A hit counts as a use: the key becomes the most recently used. If the key is not in the cache (never stored, or already evicted), return-1and change nothing.void put(int key, int value)stores the value forkey, and the key becomes the most recently used.- If the key is already in the cache, replace its value. Nothing is evicted.
- If the key is new and the cache already holds
capacityentries, first evict the least recently used entry (remove it from the map and fromorder), then store the new one.
- Keep
ordersorted by use: the least recently used key at the front, the most recently used at the end. Every use moves the key to the end.
Tools for this language:
- The constructor
LRUCache(int capacity)saves the capacity withthis->capacity = capacity. Put the fields inprivate:and the methods inpublic:. map.find(key) != map.end()tells whether a key is stored;map[key] = valuestores or replaces a value;map.erase(key)deletes an entry;map.size()counts the entries.order.erase(remove(order.begin(), order.end(), key), order.end())removes an item by value,order.push_back(key)adds at the end, andorder.front()withorder.erase(order.begin())reads and removes the first item.
This version walks over the list of keys for each operation, which is fine for learning. Interview solutions reach O(1) with a hash map plus a doubly linked list.
You start with the code that tests your class (write LRUCache above it):
class Solution {
public:
void mainFunction() {
LRUCache c(2);
c.put(1, 10);
c.put(2, 20);
cout << "Get 1: " << c.get(1) << endl;
c.put(3, 30);
cout << "Get 2: " << c.get(2) << endl;
cout << "Get 3: " << c.get(3) << endl;
cout << "Miss 9: " << c.get(9) << endl;
c.put(1, 11);
cout << "Update 1: " << c.get(1) << endl;
c.put(4, 40);
cout << "Evicted 3: " << c.get(3) << endl;
cout << "Kept 1: " << c.get(1) << endl;
cout << "Kept 4: " << c.get(4) << endl;
LRUCache s(1);
s.put(1, 100);
s.put(2, 200);
cout << "Small 1: " << s.get(1) << endl;
cout << "Small 2: " << s.get(2) << endl;
}
};
Expected output:
Get 1: 10
Get 2: -1
Get 3: 30
Miss 9: -1
Update 1: 11
Evicted 3: -1
Kept 1: 11
Kept 4: 40
Small 1: -1
Small 2: 200
Check it against the rules: Get 2 is -1 because key 2 was the least recently used when key 3 arrived (key 1 had just been read). Updating key 1 to 11 does not evict anything. With capacity 1, the second put evicts the first key.
Two structures work together: the map gives a fast lookup of the value, and
order remembers which key was used when. Both must always hold the same keys.
order.erase(remove(order.begin(), order.end(), key), order.end()) then order.push_back(key) moves a key to the end. order.front() is the least recently used key.
class LRUCache {
private:
int capacity;
unordered_map<int, int> map;
vector<int> order;
public:
LRUCache(int capacity) {
this->capacity = capacity;
}
int get(int key) {
if (map.find(key) == map.end()) return -1;
order.erase(remove(order.begin(), order.end(), key), order.end());
order.push_back(key);
return map[key];
}
void put(int key, int value) {
if (map.find(key) != map.end()) {
order.erase(remove(order.begin(), order.end(), key), order.end());
} else if ((int)map.size() >= capacity) {
int evict = order.front();
order.erase(order.begin());
map.erase(evict);
}
map[key] = value;
order.push_back(key);
}
};
Free plan: 30 tutor messages. Premium is unlimited.
