zoukankan      html  css  js  c++  java
  • 力扣146题、460题(LRU缓存机制、LFU算法)

    146.LRU缓存机制

    基本思想:

    需要做到两点:

    1.

    快读找到某个key是否有对应的val。(用字典)

    2.

    要支持在任意的位置快速插入和删除元素。(用链表)

    结合起来就是哈希链表

    使用双向链表和哈希表结合

    为什么使用双向链表?

    具体实现:

    对哈希双向链表的操作

    1.在第一个节点处添加节点 addToHead()

    2.删除其中一个节点removeNode()

    3.将一个节点移至第一个节点处moveToHead()

    4.删除最后一个节点removeTail()

    规定越靠近头部是最新的元素,越靠近尾部是越旧的元素

    代码:

    class DLinkedNode:
        def __init__(self, key=0, value=0):
            self.key = key
            self.value = value
            self.prev = None
            self.next = None
    
    
    class LRUCache:
    
        def __init__(self, capacity: int):
            self.cache = dict() # 创建一个空字典
            # 使用伪头部和伪尾部节点    
            self.head = DLinkedNode() #创建一个头结点
            self.tail = DLinkedNode()   #创建一个尾节点
            self.head.next = self.tail
            self.tail.prev = self.head
            self.capacity = capacity
            self.size = 0
    
        def get(self, key: int) -> int:
            if key not in self.cache:
                return -1
            # 如果 key 存在,先通过哈希表定位,再移到头部
            node = self.cache[key] #node是通过put创建出的节点,有key有value
            self.moveToHead(node)
            return node.value
    
        def put(self, key: int, value: int) -> None:
            if key not in self.cache:
                # 如果 key 不存在,创建一个新的节点
                node = DLinkedNode(key, value)
                # 添加进哈希表
                self.cache[key] = node # node的key给字典的key,node的value给字典的value???
                # 添加至双向链表的头部
                self.addToHead(node)
                self.size += 1
                if self.size > self.capacity:
                    # 如果超出容量,删除双向链表的尾部节点
                    removed = self.removeTail()
                    # 删除哈希表中对应的项
                    self.cache.pop(removed.key)
                    self.size -= 1
            else:
                # 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
                node = self.cache[key]
                node.value = value
                self.moveToHead(node)
        
        def addToHead(self, node):
            node.prev = self.head
            node.next = self.head.next
            self.head.next.prev = node
            self.head.next = node
        
        def removeNode(self, node):
            node.prev.next = node.next
            node.next.prev = node.prev
    
        def moveToHead(self, node):
            self.removeNode(node)
            self.addToHead(node)
    
        def removeTail(self):
            node = self.tail.prev
            self.removeNode(node)
            return node

    460、LFU算法

    基本思想:

     具体实现:

    定义两个字典:

    1.第一个:key-to-node,

    key对应的value

    2.第二个:双向链表

    freq对应key与value

    目的:利用两个哈希表来使得两个操作的时间复杂度均为O(1)

    get(key)操作:

    判断key是否存在

    存在的话,在原有的频率对应的列表[key:value;key:value;key:value]中,删除这个key:value

    判断是否改变了最小频率

    将这个key对应的freq+1

    再将这key:value放入新的freq对应的列表中

    put(key,value)操作:

    插入的key如果原来就存在,做法如同get

    插入的key不存在的话

    判断容量是否满了,满了就删除最小频率对应的key

    不满的话就是一个新的key,出现频率为1

    代码:

    # 定义节点
    class Node:
        def __init__(self, key, val):
            self.key = key
            self.val = val
            self.freq = 1
            self.prev = self.next = None
    
    # 定义双向链表
    class DLinkedList:
        def __init__(self):
            self.dummy = Node(None, None)
            # 成环,初始双向列表中只有一个节点
            self.dummy.next = self.dummy
            self.dummy.prev = self.dummy
            self.size = 0
    
        def append(self, node: Node):
            # 尾插入, 加到双向链表尾部  自己画图画图画图
            node.prev = self.dummy.prev
            node.next = self.dummy
            node.prev.next = node
            self.dummy.prev = node
            self.size += 1
    
        def pop(self, node: Node = None):
            if self.size == 0:
                return
            # 删除头部 自己画图画图画图
            if node is None:
                node = self.dummy.next
            node.prev.next = node.next
            node.next.prev = node.prev
            self.size -= 1
            return node
    
    
    class LFUCache:
    
        def __init__(self, capacity: int):
            from collections import defaultdict
            self.key_to_node = {}
            self.freq = defaultdict(DLinkedList)
    # freq:[key:value;key:value] self.min_freq
    = 0 self.capacity = capacity def get(self, key: int) -> int: if key not in self.key_to_node: return -1 node = self.key_to_node[key] node_freq = node.freq self.freq[node_freq].pop(node)
    #self.freq[node_freq] 是[key:value;key:value;....]
    if self.min_freq == node_freq and self.freq[node_freq].size == 0: self.min_freq += 1 node.freq += 1 self.freq[node.freq].append(node) return node.val def put(self, key: int, value: int) -> None: if self.capacity == 0: return if key in self.key_to_node: node = self.key_to_node[key] node_freq = node.freq self.freq[node_freq].pop(node) if self.min_freq == node_freq and self.freq[node_freq].size == 0: self.min_freq += 1 node.freq += 1 node.val = value self.freq[node.freq].append(node) else: if len(self.key_to_node) == self.capacity: node = self.freq[self.min_freq].pop() self.key_to_node.pop(node.key) node = Node(key, value) self.key_to_node[key] = node self.freq[1].append(node) self.min_freq = 1

    不是O(1)时间但比O(1)时间的容易想到和简单直观,应对面试更实际一点吧

    二分法,主要通过维护一个按(频率,时间)有序的key列表和一个{key:值,频率,时间}的哈希实现

    每次从key里取出值和频率时间后再根据频率时间在key列表里找到该记录并移除后再更新并二分插入即可

    from bisect import bisect_left, insort
    class LFUCache:
        def __init__(self, capacity: int):
            self.cap, self.tick = capacity, 0  # 容量和计时
            self.his = []  # 元素形式为:(freq, tick, key)
            self.dic = {}  # 键值对形式为:key:[val, freq, tick]
    
        def get(self, key: int) -> int:
            if key not in self.dic:  # key不存在
                return -1
            self.tick += 1  # 计时
            val, freq, tick = self.dic[key]  # 取出值、频率和时间
            self.dic[key][1] += 1  # 将频率+1
            self.his.pop(bisect_left(self.his, (freq, tick, key)))  # 找到history里的记录并移除
            insort(self.his, (freq+1, self.tick, key))  # 将更新后的记录二分插入
            return val
    
        def put(self, key: int, value: int) -> None:
            if not self.cap:
                return
            self.tick += 1
            if key in self.dic:
                _, freq, tick = self.dic[key]  # 取出频率和时间
                self.dic[key][:] = value, freq+1, self.tick  # 更新值、频率和计时
                self.his.pop(bisect_left(self.his, (freq, tick, key)))  # 找到history里的记录并移除
                insort(self.his, (freq+1, self.tick, key))  # 将更新后的记录二分插入
            else:  # 无该记录
                self.dic[key] = [value, 1, self.tick]
                if len(self.his) == self.cap:  # history容量已满
                    del self.dic[self.his.pop(0)[2]]  # 移除history首个元素即对应的键值对
                insort(self.his, (1, self.tick, key))  # 将新记录插入history
  • 相关阅读:
    linux中~和/的区别
    Linux centos 7安装
    xshell远程连接虚拟机
    虚拟机Linux不能上网简单有效的解决办法
    visudo
    users
    TreeSizeFree(硬盘文件整理)
    dos2unix
    iconv
    PS1系统变量
  • 原文地址:https://www.cnblogs.com/zhaojiayu/p/14484806.html
Copyright © 2011-2022 走看看