题目:
题目链接: https://leetcode-cn.com/problems/lfu-cache/
解题思路:
LFU全称为Least Frequently Used,最近最少使用,算法的思路是:如果一个数据使用的次数很少,那么在将来被使用的概率应该也比较低,所以在有限的内存空间下,当内存快满、或者已经满了的情况下,优先删除此部分数据,以便于节省空间给其他数据
LFU与LRU想对应,LRU全称为Least Recently Used,最近最久未使用,算法的思路是:如果一个数据很久都未被使用,那么在之后的一段时间内,被使用的概率也比较低,所以优先删除此部分数据
LFU的思路是基于使用次数的,LRU的思路是基于使用时间的
回到本题,为了实现O(1)的时间复杂度,最常用的方式为:
查找 : 使用hash表
插入 :链表
单向链表需在删除已有元素时,需要遍历链表,才能将需要删除的节点前后节点连接起来,所以将链表修改为双向链表
查找 : hash表
插入 + 删除 : 双向链表
本题的整体思路就比较清晰了:
变量:
- keyMap : hash表,key值为put时的key值,value值为在freqMap保存的双向链表中的节点地址(指针)
- freqMap : hash表,key值为使用频率,value值为当前使用频率按照时间序的双向链表,链表每个节点中保存的值为:
- put时的key值
- put时的value值
- 当前值的使用频率,因为从keyMap找到时,无法知道freqMap中的key值,所以需要在node中保存此值
- 当前节点的上个节点prev
- 当前节点的下个节点next
- capacity : 最大可以保存的值个数
- size : 当前已有的值个数
- minFreq : 当前使用频率最小的频率
流程:
- get(self, key: int)操作:
- 在keyMap中查找key值,如果不存在,返回-1;如果存在,获取到对应的节点node
- 更新node对应的频率freq = freq + 1,在当前链表中删除此节点
- 将更新freq后的node重新插入到freqMap中,新freq对应的链表头,以便保证链表整体是有序的
- 返回node中保存的value值
- put(self, key: int, value: int)操作:
- 在keyMap中查找key值,如果不存在,插入新的key值到keyMap和freqMap中;如果存在,则更新node中对应的freq和value值,并更新node的位置,更新方式同get操作中的2、3、4步骤
- 更新已保存的值个数size值
- 如果size <= capacity,则直接退出;如果size > capacity,则根据minFreq,删除对应链表中的最后一个节点
代码实现:
class Node:
def __init__(self, key: int, value: int, freq = 0, prev_node = None, next_node = None):
self.key = key
self.value = value
self.freq = freq
self.prev_node = prev_node
self.next_node = next_node
def insert(self, node) -> None:
# insert操作为常用操作,添加一个接口便于使用
node.prev_node = self
node.next_node = self.next_node
self.next_node.prev_node = node
self.next_node = node
def create_linked_lst() -> set((Node, Node)):
# 创建首尾dummy节点,避免一些临界情况的判断
# 并且方便管理
head = Node(0,0)
tail = Node(0,0)
head.next_node = tail
tail.prev_node = head
return (head, tail)
class LFUCache:
def __init__(self, capacity: int):
self.__freq_map = collections.defaultdict(create_linked_lst)
self.__key_map = {}
self.__capacity = capacity
self.__size = 0
self.__min_freq = 0
def __delete_node(self, node: Node) -> int:
# 在put中调用__update_node时,node尚未设置前后置节点
# 此时node.prev_node是None,需要保护性判断
if node.prev_node:
node.prev_node.next_node = node.next_node
node.next_node.prev_node = node.prev_node
# 如果删除完成后,当前频率下没有有效节点了,则在__freq_map频率表中删除此列表
if node.prev_node is self.__freq_map[node.freq][0] and node.next_node is self.__freq_map[node.freq][-1]:
self.__freq_map.pop(node.freq)
return node.key
def __update_node(self, node: Node):
node.freq += 1
self.__delete_node(node)
self.__freq_map[node.freq][-1].prev_node.insert(node)
if node.freq == 1:
# 如果当前频率是1,不可能有比1还小的频率,此时更新最小频率为1
self.__min_freq = 1
elif self.__min_freq == node.freq - 1:
# 如果最小频率是当前node更新前的频率,需要判断之前的频率链表中,是否还有有效节点
# 如果没有,则删除对应的链表
head, tail = self.__freq_map[node.freq - 1]
if head.next_node is tail:
self.__min_freq = node.freq
def get(self, key: int) -> int:
if key in self.__key_map:
# 更新节点位置
self.__update_node(self.__key_map[key])
return self.__key_map[key].value
return -1
def put(self, key: int, value: int) -> None:
if self.__capacity > 0:
if key in self.__key_map:
# 如果当前key已经在__key_map中,则更新对应的值
node = self.__key_map[key]
node.value = value
else:
# 如果不存在,则创建一个新节点
node = Node(key, value)
self.__key_map[key] = node
self.__size += 1
if self.__size > self.__capacity:
# 节点个数已满,删除使用频率最小的存在时间最长的节点
self.__size -= 1
del_key = self.__delete_node(self.__freq_map[self.__min_freq][0].next_node)
self.__key_map.pop(del_key)
self.__update_node(node)