1+ /* *
2+ * @file lru_cache.cpp
3+ * @brief Implementation of the Least Recently Used (LRU) Cache.
4+ *
5+ * Description:
6+ * An LRU Cache is a fixed-size cache that evicts the least recently used item
7+ * when it reaches its capacity. This implementation uses a combination of a
8+ * hash map (std::unordered_map) and a doubly-linked list (std::list) to
9+ * achieve O(1) average time complexity for both `get` and `put` operations.
10+ *
11+ * - The `list` stores pairs of key-value to maintain the order of usage.
12+ * The most recently used items are moved to the front of the list.
13+ * - The `std::unordered_map` stores the key and an iterator to its corresponding
14+ * node in the list, allowing for O(1) access to any item.
15+ *
16+ * Complexity Analysis:
17+ * - Time Complexity: O(1) for both `get` and `put` operations.
18+ * - `get`: Hash map lookup is O(1) on average. Moving the element to the front of the list is also O(1).
19+ * - `put`: Hash map lookup and insertion are O(1) on average. List operations (push_front, pop_back, erase) are O(1).
20+ * - Space Complexity: O(capacity), as the cache stores up to 'capacity' elements.
21+ */
22+ #include < bits/stdc++.h>
23+ using namespace std ;
24+
25+ class LRUCache {
26+ private:
27+ int capacity;
28+ // Doubly-linked list to store {key, value} pairs.
29+ // The front of the list is the most recently used.
30+ list<pair<int , int >> items_list;
31+ // Hash map to store key -> list iterator for O(1) lookup.
32+ unordered_map<int , list<pair<int , int >>::iterator> items_map;
33+
34+ public:
35+ // Constructor to initialize the cache with a given capacity.
36+ // Capacity must be a positive integer.
37+ LRUCache (int cap) : capacity(cap) {}
38+
39+ int get (int key) {
40+ // Check if the key exists in the map.
41+ if (items_map.find (key) == items_map.end ()) {
42+ return -1 ; // Key not found
43+ }
44+
45+ // Key found, get the iterator from the map.
46+ auto it = items_map[key];
47+ // The value is the second element of the pair in the list.
48+ int value = it->second ;
49+
50+ // Move the accessed item to the front of the list to mark it as most recently used.
51+ // splice() efficiently moves the node without creating/destroying elements.
52+ items_list.splice (items_list.begin (), items_list, it);
53+
54+ return value;
55+ }
56+
57+ // put key-value pair into the cache
58+ // If the key already exists, update its value and move it to the front.
59+ // If the cache is at capacity, evict the least recently used item.
60+ void put (int key, int value) {
61+ // If the key already exists, update its value and move it to the front.
62+ if (items_map.find (key) != items_map.end ()) {
63+ auto it = items_map[key];
64+ it->second = value; // Update value
65+ items_list.splice (items_list.begin (), items_list, it); // Move to front
66+ return ;
67+ }
68+
69+ // If the cache is at full capacity, evict the least recently used item.
70+ if (items_map.size () == capacity) {
71+ // The least recently used item is at the back of the list.
72+ pair<int , int > last_item = items_list.back ();
73+ items_map.erase (last_item.first ); // Remove from map
74+ items_list.pop_back (); // Remove from list
75+ }
76+
77+ // Insert the new key-value pair at the front of the list.
78+ items_list.push_front ({key, value});
79+ // Store the iterator to the new item in the map.
80+ items_map[key] = items_list.begin ();
81+ }
82+ };
83+
84+ // Example usage and test cases
85+ int main () {
86+ // Create a cache with capacity 2
87+ LRUCache cache (2 );
88+
89+ cout << " Putting (1, 10)" << endl;
90+ cache.put (1 , 10 );
91+ cout << " Putting (2, 20)" << endl;
92+ cache.put (2 , 20 );
93+
94+ // Test basic get
95+ cout << " Getting key 1: " << cache.get (1 ) << endl; // returns 10
96+
97+ // Test eviction: putting 3 should evict key 2 (least recently used)
98+ cout << " Putting (3, 30)" << endl;
99+ cache.put (3 , 30 );
100+ cout << " Getting key 2: " << cache.get (2 ) << endl; // returns -1 (not found)
101+
102+ // Test that key 1 is still present and now is the LRU
103+ cout << " Getting key 1: " << cache.get (1 ) << endl; // returns 10
104+
105+ // Test updating an existing key
106+ cout << " Putting (1, 100) to update" << endl;
107+ cache.put (1 , 100 );
108+ cout << " Getting key 1: " << cache.get (1 ) << endl; // returns 100
109+
110+ return 0 ;
111+ }
0 commit comments