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.
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
LeetCode:链接
双向链表 + hash table。
缓存是一种提高数据读取性能的技术,在硬件设计、软件开发中都有着非常广泛的应用,比如常见的 CPU 缓存、数据库缓存、浏览器缓存等等。
缓存的大小有限,当缓存被用满时,哪些数据应该被清理出去,哪些数据应该被保留?这就需要缓存淘汰策略来决定。常见的策略有三种:先进先出策略 FIFO(First In,First Out)、最少使用策略 LFU(Least Frequently Used)、最近最少使用策略 LRU(Least Recently Used)。
为了能够快速删除最久没有访问的数据项和插入最新的数据项,我们使用双向链表连接Cache中的数据项,并且保证链表维持数据项从最近访问到最旧访问的顺序。每次数据项被查询到时,都将此数据项移动到链表头部(O(1)的时间复杂度)。这样,在进行过多次查找操作后,最近被使用过的内容就向链表的头移动,而没有被使用的内容就向链表的后面移动。当需要替换时,链表最后的位置就是最近最少被使用的数据项,我们只需要将最新的数据项放在链表头部,当Cache满时,淘汰链表最后的位置就是了。
注: 对于双向链表的使用,基于两个考虑。首先是Cache中块的命中可能是随机的,和Load进来的顺序无关。其次,双向链表插入、删除很快,可以灵活的调整相互间的次序,时间复杂度为O(1)。
查找一个链表中元素的时间复杂度是O(n),每次命中的时候,我们就需要花费O(n)的时间来进行查找,如果不添加其他的数据结构,这个就是我们能实现的最高效率了。目前看来,整个算法的瓶颈就是在查找这里了,怎么样才能提高查找的效率呢?Hash表,对,就是它,数据结构中之所以有它,就是因为它的查找时间复杂度是O(1)。
梳理一下思路:对于Cache的每个数据块,我们设计一个数据结构来储存Cache块的内容,并实现一个双向链表,其中属性next和prev是双向链表的两个指针,key用于存储对象的键值,value用于存储cache块对象本身。
class Node(object):
def __init__(self, key, value):
self.key = key
self.value = value
self.prev = None
self.next = None
class DoubleLinkedList(object):
def __init__(self):
self.head = None
self.tail = None
def removeLast(self):
self.remove(self.tail)
def remove(self, node):
# 说明双向链表为空或者只有一个结点
if self.head == self.tail:
self.head = self.tail = None
return
elif node == self.head:
node.next.prev = None
self.head = node.next
return
elif node == self.tail:
node.prev.next = None
self.tail = node.prev
return
node.next.prev = node.prev
node.prev.next = node.next
def addFirst(self, node):
# 如果链表为空时添加新结点的情况
if not self.head:
self.head = self.tail = node
node.next = node.prev = None
return
node.next = self.head
self.head.prev = node
# 重新定义新的头结点
self.head = node
node.prev = None
class LRUCache(object):
def __init__(self, capacity):
"""
:type capacity: int
"""
self.capacity = capacity
self.size = 0
self.cache = DoubleLinkedList()
self.P = dict()
def get(self, key):
"""
:type key: int
:rtype: int
"""
if key in self.P:
# 当查询一次之后就要把当前的节点删掉 插入到头结点
self.cache.remove(self.P[key])
self.cache.addFirst(self.P[key])
return self.P[key].value
else:
return -1
def put(self, key, value):
"""
:type key: int
:type value: int
:rtype: void
"""
if key in self.P:
# 如果在字典里 先删除原来的位置
self.cache.remove(self.P[key])
# 将字典中的value值替换成新值
self.P[key].value = value
# 再添加到头结点
self.cache.addFirst(self.P[key])
else:
node = Node(key, value)
# 字典储存的是结点值
self.P[key] = node
self.cache.addFirst(node)
self.size += 1
if self.size > self.capacity:
self.size -= 1
'''必须先删除字典的尾结点的值,然后再从双向链表删除!!'''
del self.P[self.cache.tail.key]
'''如果先删除双向链表,字典中的尾结点就不再是应该删除的节点了'''
self.cache.removeLast()
# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)