原题链接:链接
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put.
get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(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.
The cache is initialized with a positive capacity.
Follow up:
Could you do both operations in O(1) time complexity?
Example:
LRUCache cache = new LRUCache( 2 /* capacity */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.put(4, 4); // evicts key 1
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4
这道题蕴含了LRU缓存算法的实现原理,该算法被广泛应用于资源信息如图片的缓存,提供了一种灵活的缓存策略。在理解这道题之前,需要了解HashMap的原理。HashMap提供了key-value的存储方式,并提供了put和get方法来进行数据存取。但是HashMap是无序的,而现实中我们希望有顺序地去存储key-value时,就需要使用LinkedHashMap了,其中LinkedHashMap的实现也就是利用了双向链表+哈希表
。Python的collection
模块有一个双向队列deque
可以辅助我们较轻松地完成此题,但为了学习,我们打算手写一遍一些算法的实现,以理清细节。
算法思路:
- 创建一个
Node
类,用于保存键、值以及前后指针的属性
class Node:
def __init__(self, key, val):
self.key = key
self.val = val
self.pre = None
self.nxt = None
- 创建一个
DList
类,其中具备头节点和尾节点,并拥有添加节点和删除节点两个方法。
class DLinked:
def __init__(self, head=None, tail=None):
self.head = head
self.tail = tail
# 双向链表添加节点的方法
def add(self, node):
pass
def remove(self, node):
pass
get(key)
:如果key
存在于保存信息的哈希表hashTable
中,则将其在链表中移除并重新加入到表头,并同时返回该值;其中表头的元素代表着最新
的元素;反之,如果key
不存在,则直接返回-1.
def get(key):
# 先取出节点
# 如果不存在 则返回None
node = self.hashTable.get(key, None)
if node:
# 双向链表实例
val = node.val
self.dlinked.remove(node)
# 并添加到表头
self.dlinked.add(node)
return val
return -1
put(key, value)
:如果key
存在于保存信息的哈希表中,同样地将相应的节点移除并重新添加到表头,同时更新value
值;如果不存在且达到了缓存的最大容量capacity
,则将最近最少使用的元素,也就是表尾的元素删除,并也将其从hashTable
中删除,将当前key, value
分别加入到dlinked
以及hashTable
中;如果不存在且未达到最大容量,则直接加入即可。
def put(key, value):
node = self.hashTable.get(key, None)
# 如果存在
if node:
self.dlinked.remove(node)
node.val = value
self.dlinked.add(node)
return
# 如果不存在且达到了最大容量
if self.capacity == len(self.hashTable):
# 获取节点
tailNode = self.dlinked.tail
# 删除
# 删除节点方法返回一个 key
oldKey = self.dlinked.remove(tailNode)
del self.hashTable[oldKey]
# 加入节点
# 加入方法返回新加这个节点
node = self.dlinked.add(Node(key, value))
self.hashTable[key] = node
# 否则 直接加入
else:
node = self.dlinked.add(Node(key, value))
self.hashTable[key] = node
return
剩下的工作就是完善双向链表的add
与remove
方法。
首先是add
方法,如果链表没有表头,则直接将节点设为头部,否则加入表头:
def add(self, node):
# 如果双向链表没有头节点
if self.head == None:
self.head = node
# 同时如果没有尾节点
if self.tail == None:
self.tail = node
else:
# 将节点加入表头
self.head.pre = node
node.nxt = self.head
node.pre = None
self.head = node
return self.head
remove
方法稍微复杂一些,需要根据删除的节点在原双向链表的位置进行判断,分别是表头、表中间和表尾处:
def remove(self, node):
# 首先获取要删除节点的前后指针
pre, nxt = node.pre, node.nxt
# 如果只有一个节点
if pre is None and nxt is None:
self.head = None
self.tail = None
return node.key
# 如果是头节点 且有后续节点
if pre is None:
nxt.pre = None
self.head = nxt
# 如果是尾节点 且有前续节点
elif nxt is None:
pre.nxt = None
self.tail = pre
# 中间节点
else:
pre.nxt = nxt
nxt.pre = pre
# 返回被删除节点的键
return node.key
最后整合一块完事。没想到LRU Cache看起来挺简单,实现起来却不轻松啊。同志仍需努力啊。