【leetcode系列】【算法】【困难】LFU缓存(hash + 双向链表,O(1)时间复杂度)

题目:

题目链接: https://leetcode-cn.com/problems/lfu-cache/

 

解题思路:

LFU全称为Least Frequently Used,最近最少使用,算法的思路是:如果一个数据使用的次数很少,那么在将来被使用的概率应该也比较低,所以在有限的内存空间下,当内存快满、或者已经满了的情况下,优先删除此部分数据,以便于节省空间给其他数据

LFU与LRU想对应,LRU全称为Least Recently Used,最近最久未使用,算法的思路是:如果一个数据很久都未被使用,那么在之后的一段时间内,被使用的概率也比较低,所以优先删除此部分数据

LFU的思路是基于使用次数的,LRU的思路是基于使用时间

 

回到本题,为了实现O(1)的时间复杂度,最常用的方式为:

查找 : 使用hash表

插入 :链表

单向链表需在删除已有元素时,需要遍历链表,才能将需要删除的节点前后节点连接起来,所以将链表修改为双向链表

查找 : hash表

插入 + 删除 : 双向链表

本题的整体思路就比较清晰了:

变量:

  1. keyMap : hash表,key值为put时的key值,value值为在freqMap保存的双向链表中的节点地址(指针)
  2. freqMap : hash表,key值为使用频率,value值为当前使用频率按照时间序的双向链表,链表每个节点中保存的值为:
    1. put时的key值
    2. put时的value值
    3. 当前值的使用频率,因为从keyMap找到时,无法知道freqMap中的key值,所以需要在node中保存此值
    4. 当前节点的上个节点prev
    5. 当前节点的下个节点next
  3. capacity : 最大可以保存的值个数
  4. size : 当前已有的值个数
  5. minFreq : 当前使用频率最小的频率

流程:

  1. get(self, key: int)操作:
    1. 在keyMap中查找key值,如果不存在,返回-1;如果存在,获取到对应的节点node
    2. 更新node对应的频率freq = freq + 1,在当前链表中删除此节点
    3. 将更新freq后的node重新插入到freqMap中,新freq对应的链表头,以便保证链表整体是有序的
    4. 返回node中保存的value值
  2. put(self, key: int, value: int)操作:
    1. 在keyMap中查找key值,如果不存在,插入新的key值到keyMap和freqMap中;如果存在,则更新node中对应的freq和value值,并更新node的位置,更新方式同get操作中的2、3、4步骤
    2. 更新已保存的值个数size值
    3. 如果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)

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值