运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?
示例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得关键字 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得关键字 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
哈希表+双指针
class DLinkedNode(object):
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache(object):
def __init__(self, capacity):
"""
:type capacity: int
"""
self.dic = {}
self.dummyHead = DLinkedNode()
self.dummyTail = DLinkedNode()
self.dummyHead.next = self.dummyTail
self.dummyTail.prev = self.dummyHead
self.capacity = capacity
self.size = 0
def get(self, key):
"""
:type key: int
:rtype: int
"""
if key not in self.dic:
return -1
else:
node = self.dic[key]
self.moveToHead(node)
return node.value
def put(self, key, value):
"""
:type key: int
:type value: int
:rtype: None
"""
if key in self.dic:
node = self.dic[key]
node.value = value
self.moveToHead(node)
else:
node = DLinkedNode(key=key, value=value)
self.dic[key] = node
self.insertIntoHead(node)
self.size += 1
if self.size > self.capacity:
deletedNode = self.deleteTail()
del self.dic[deletedNode.key]
def moveToHead(self, node):
prev = node.prev
next = node.next
prev.next = next
next.prev = prev
headNext = self.dummyHead.next
self.dummyHead.next = node
node.prev = self.dummyHead
node.next = headNext
headNext.prev = node
def insertIntoHead(self, node):
headNext = self.dummyHead.next
self.dummyHead.next = node
node.prev = self.dummyHead
node.next = headNext
headNext.prev = node
def deleteTail(self):
prevNode = self.dummyTail.prev
prevPrevNode = prevNode.prev
prevPrevNode.next = self.dummyTail
self.dummyTail.prev = prevPrevNode
prevNode.next = None
prevNode.preb = None
return prevNode
# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)
由于题目需要读写的时间复杂度为O(1),我们会想到用哈希表来储存数据。但是由于需要给每个数据更新状态,使其在写入数据时如果出现达到容量上限需要删除数据,删除最久未使用的数据。那么一个哈希表显然就不够用了,无法同时保存时间信息。这时候就需要在哈希表的基础上再引入双向链表。我们使用哈希表来保存key和其对应的链表中的节点,这样我们通过哈希表就能以O(1)的时间效率找到对应的节点,然后在节点上获取key对应的value。那我们又如何保证时间戳的呢?我们让这些节点按时间排序,使用过的节点放在最前面,放在越后面表示越久没有使用。每当我们进行更新操作或者是读取操作时,只需要将对应的节点放到链表头部,就能保证时间顺序。那我们为什么需要使用双链表而不是单链表呢,因为如果使用单链表无法获得前一个节点,需要从头开始遍历,这样时间复杂度就不是O(1)而是O(n)了。
在设计时我们还添加了一个伪头节点和伪尾节点,这样做的好处是当我们在删除或者添加节点时不需要再判断左右节点是否为空了。