Leetcode 146 LRU Cache
Soru
Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.
Implement the LRUCache class:
- LRUCache(int capacity) Initialize the LRU cache with positive size capacity.
- int get(int key) Return the value of the key if the key exists, otherwise return -1.
- void put(int key, int value) Update the value of the key if the key exists. Otherwise, add the key-value pair to the cache. If the number of keys exceeds the capacity from this operation, evict the least recently used key.
The functions get and put must each run in O(1) average time complexity.
Örnek 1
Input
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
Output
[null, null, null, 1, null, -1, null, -1, 3, 4]
Explanation
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // cache is {1=1}
lRUCache.put(2, 2); // cache is {1=1, 2=2}
lRUCache.get(1); // return 1
lRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3}
lRUCache.get(2); // returns -1 (not found)
lRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3}
lRUCache.get(1); // return -1 (not found)
lRUCache.get(3); // return 3
lRUCache.get(4); // return 4
Çözüm
- “146. LRU Cache” sorusu, en az kullanılan öğe (Least Recently Used - LRU) önbellek sistemi tasarlamayı ister. Bu önbellek, belirli bir kapasiteye sahiptir ve bu kapasite aşıldığında, en az kullanılan öğe önbellekten atılır ve yeni öğe eklenir. Sorun, bu önbellek mekanizmasını verimli bir şekilde uygulamayı gerektirir, bu da hem get hem de put işlemlerinin O(1) zaman karmaşıklığında çalışmasını zorunlu kılar.
- Girdi: Belirli bir kapasite ile başlatılacak bir LRU önbelleği.
- Çıktı: get(key) ve put(key, value) işlemleri üzerinden dinamik olarak yönetilen bir önbellek.
- LRU önbelleğini verimli bir şekilde uygulamak için sıklıkla çift yönlü bağlı liste (doubly linked list) ve hash tablosu (dictionary) kullanılır:
- Çift Yönlü Bağlı Liste: Bu liste, öğelerin sırasını korur. En son kullanılan öğeler liste başına yakın, en az kullanılanlar ise liste sonuna yakın yer alır.
- Hash Tablosu: Anahtarlar ve bağlı listedeki düğümlerin referanslarını saklar, bu sayede herhangi bir öğeye O(1) zamanda erişim sağlanır.
- Çalışma Mekanizması:
- İnitializasyon: Dummy baş ve son düğümleri ile çift yönlü bir liste oluşturulur.
- Node Ekleme ve Çıkarma: Öğelerin kolayca eklenebilmesi ve çıkarılabilmesi için yardımcı fonksiyonlar kullanılır.
- Get ve Put İşlemleri: get işlemi, öğeyi erişildiğinde başa taşır. put işlemi, yeni bir öğe eklerken kapasiteyi aştığı takdirde en son öğeyi kaldırır.
Code
class ListNode:
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity: int):
self.cache = {}
self.capacity = capacity
self.head = ListNode()
self.tail = ListNode()
self.head.next = self.tail
self.tail.prev = self.head
def removeNode(self, node):
prev, nxt = node.prev, node.next
prev.next, nxt.prev = nxt, prev
def addNode(self, node):
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def moveToHead(self, node):
self.removeNode(node)
self.addNode(node)
def popTail(self):
res = self.tail.prev
self.removeNode(res)
return res
def get(self, key: int) -> int:
node = self.cache.get(key, None)
if not node:
return -1
self.moveToHead(node)
return node.value
def put(self, key: int, value: int) -> None:
node = self.cache.get(key)
if not node:
newNode = ListNode(key, value)
self.cache[key] = newNode
self.addNode(newNode)
if len(self.cache) > self.capacity:
tail = self.popTail()
del self.cache[tail.key]
else:
node.value = value
self.moveToHead(node)
Complexity
- Time complexity (Zaman Karmaşıklığı): Her işlem O(1) zamanında çalışır çünkü doğrudan hash tablosu erişimi ve sabit zamanlı düğüm ekleme/çıkarma işlemleri kullanılır.
- Space complexity (Alan Karmaşıklığı): O(capacity), çünkü önbellek boyutu kapasite ile sınırlıdır ve bu kapasitede düğüm ve hash girişleri saklanır.