https://leetcode-cn.com/problems/lru-cache/
设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
LRU 缓存淘汰算法就是一种常用策略。LRU 的全称是 Least Recently Used,最近使用过的数据应该是是「有用的」,很久都没用过的数据应该是无用的,内存满了就优先删那些很久没用过的数据。
举例,安卓手机软件的后台运行,比如先后打开了「设置」「手机管家」「日历」等应用。
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)
cache = LRUCache(2)
cache.put(1, 1) # put(key, value)
cache.put(2, 2)
# get(key), 获取关键字的值(总是正数),否则返回 -1
print(cache.get(1)) # 返回 1
cache.put(3, 3) # 该操作会使得密钥 2 作废
print(cache.get(2)) # 返回 -1 (未找到)
cache.put(4, 4) # 该操作会使得密钥 1 作废
print(cache.get(1)) # 返回 -1 (未找到)
print(cache.get(3)) # 返回 3
print(cache.get(4)) # 返回 4
class LRUCache:
def __init__(self, capacity: int):
def get(self, key: int) -> int:
def put(self, key: int, value: int) -> None:
字典存k-v:用字典来存储 key-value 结构,这样对于查找操作时间复杂度就是 O(1)。
双向链表:但是因为字典本身是无序的,还需要一个类似于队列的结构来记录访问的先后顺序。这个队列需要支持如下几种操作
class ListNode:
def __init__(self, key=None, value=None):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
# init函数
def __init__(self, capacity: int):
self.capacity = capacity
self.hashmap = {} # 用字典来存储 key-value,查找操作O(1)
# 新建两个节点 head 和 tail
self.head = ListNode()
self.tail = ListNode()
# 初始化链表为 head <-> tail
self.head.next = self.tail
self.tail.prev = self.head
# 因为get与put操作都可能需要将双向链表中的某个节点移到头部(变成最新访问的)
# 子方法:将该节点移到链表头部
def move_node_to_header(self, key):
# (1)先将哈希表key指向的节点拎出来,为了简洁起名node
# hashmap[key] hashmap[key]
# | |
# V --> V
# prev <-> node <-> next pre <-> next ... node
node = self.hashmap[key]
node.prev.next = node.next # prev -> next
node.next.prev = node.prev # prev <- next
# (2)之后将node插入到头部节点前
# hashmap[key] hashmap[key]
# | |
# V --> V
# header <-> next ... node header <-> node <-> next
node.prev = self.head # head <- node
node.next = self.head.next # node -> next
self.head.next.prev = node # node <- next
self.head.next = node # head -> node
# 子方法:在头部插入节点new
def add_node_to_header(self, key, value):
new = ListNode(key, value)
self.hashmap[key] = new
# head <-> new <-> head.next
new.prev = self.head # head <- new
new.next = self.head.next # new -> head.next
self.head.next.prev = new # new <- head.next
self.head.next = new # head -> new
# 子方法:删除尾结点(若cache容量已满)
def pop_tail(self):
last_node = self.tail.prev
# 去掉链表尾部的节点在哈希表的对应项
self.hashmap.pop(last_node.key)
# 去掉最久没有被访问过的节点,即尾部Tail之前的一个节点
# last_node.prev - last_node - tail
last_node.prev.next = self.tail
self.tail.prev = last_node.prev
# last_node.prev <-> tail
return last_node
# get,O(1)
def get(self, key: int) -> int:
if key in self.hashmap:
# 如果已经在链表中了就把它移到头部(变成最新访问的)
self.move_node_to_header(key)
res = self.hashmap.get(key, -1)
if res == -1:
return res
else:
return res.value
# put,O(1)
def put(self, key: int, value: int) -> None:
if key in self.hashmap:
# 如果key本身已经在哈希表中了就不需要在链表中加入新的节点
# 但是需要更新字典该值对应节点的value
self.hashmap[key].value = value
# 之后将该节点移到链表头部
self.move_node_to_header(key)
else:
if len(self.hashmap) >= self.capacity:
# 若cache容量已满,删除cache中最不常用的节点
self.pop_tail()
self.add_node_to_header(key, value)
https://leetcode-cn.com/problems/lru-cache/solution/shu-ju-jie-gou-fen-xi-python-ha-xi-shuang-xiang-li/
https://leetcode-cn.com/problems/lru-cache/solution/lru-ce-lue-xiang-jie-he-shi-xian-by-labuladong/