LRU Cache
Question
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get
and `set.
get(key)
- Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
set(key, value)
- Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.
Thoughts
LRU cache数据结构的核心就是当存储空间满了,而有新的item要插入时,要先丢弃最早更新的item。这里的数据结构需要符合以下条件:
要能快速找到最早更新的item。这里需要将item以更新时间顺序排序。 可选的有:queue,heap,linked list
要能快速访问指定item,并且访问以后要更新它的时间顺序。 对于更新时间顺序这个操作,queue和heap要做到就很困难了。所以这点最佳的是linked list。但linked list中查找指定item需要遍历,这里可以用一个hash table来记录key与节点之间的对应。并且由于要随时更新节点位置,doubly linked list更为适用。(也可以不用doubly linked list, 在hashmap总存listnode的前一个,并且用head和tail来track list的头尾就可以)
linked list最尾部的表示最新访问的
get(key)
- 把node放到尾部
- 读取value
set(key, value)
如果key已经有了
- 放到尾部
- 更新value
如果key还没有
- 放到尾部
- 如果 size > capacity, 讲头部的node pop掉
因为头部和尾部都不确定,所以就像linkedlist要有dummy node一样,要有一个head和tail
head一直不变,指向第一个node(表示least recently used) tail一直指向最后一个node,每次更新(表示mose recently used)
Solution
class LinkedNode:
def __init__(self, key=None, value=None, next=None):
self.key = key
self.value = value
self.next = next
class LRUCache:
# @param capacity, an integer
def __init__(self, capacity):
# write your code here
#hash
#key: key
#value: prev ListNode in the linkedlist
self.hash = {}
self.head = LinkedNode()
self.tail = self.head
self.capacity = capacity
def pushTail(self, node):
self.hash[node.key] = self.tail
self.tail.next = node
self.tail = node
node.next = None
def popFront(self):
del self.hash[self.head.next.key]
self.head.next = self.head.next.next
self.hash[self.head.next.key] = self.head
def updateLink(self, prev):
node = prev.next
#if already tail, no need to update
if node == self.tail:
return
else:
prev.next = node.next
if node.next:
self.hash[node.next.key] = prev
# @return an integer
def get(self, key):
# write your code here
if key not in self.hash:
return -1
else:
#key in hash
#1. update node's prev and next
#2. put node to tail
prev = self.hash[key]
node = prev.next
if node != self.tail:
self.updateLink(prev)
self.pushTail(node)
return self.hash[key].next.value
# @param key, an integer
# @param value, an integer
# @return nothing
def set(self, key, value):
# write your code here
if key in self.hash:
#key in hash
#1. update node's prev and next
#2. put node to tail
prev = self.hash[key]
node = prev.next
if node != self.tail:
self.updateLink(prev)
self.pushTail(node)
self.hash[key].next.value = value
else:
node = LinkedNode(key, value)
#1. put node to tail
#2. if size > cap, pop lru node
self.pushTail(node)
if len(self.hash) > self.capacity:
self.popFront()
the part to determine if it's tail & updateLink and pushTail can be combined into updateLink()
def updateLink(self, prev):
node = prev.next
#if already tail, no need to update
if node == self.tail:
return
else:
prev.next = node.next
if node.next:
self.hash[node.next.key] = prev
#change
self.pushTail(node)
# @return an integer
def get(self, key):
# write your code here
if key not in self.hash:
return -1
else:
#change
prev = self.hash[key]
self.updateLink(prev)
return self.hash[key].next.value
# @param key, an integer
# @param value, an integer
# @return nothing
def set(self, key, value):
# write your code here
if key in self.hash:
#change
prev = self.hash[key]
self.updateLink(prev)
self.hash[key].next.value = value
else:
node = LinkedNode(key, value)
#1. put node to tail
#2. if size > cap, pop lru node
self.pushTail(node)
if len(self.hash) > self.capacity:
self.popFront()