• 笔试题-LRU


    最近想锻炼一下笔试能力。翻开牛客题目,做一下LRU。

    题目要求空间复杂度为O(1),我的代码超时了。待优化,贴下来。等我搞懂了再写详细点

    ··········第一次尝试 ROUND 1··········

    ### 题目要求
    设计LRU缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能
    set(key, value):将记录(key, value)插入该结构
    get(key):返回key对应的value值
    [要求]
    set和get方法的时间复杂度为O(1)
    某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的。
    当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。
    若opt=1,接下来两个整数x, y,表示set(x, y)
    若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1
    对于每个操作2,输出一个答案
    
    输入输出实例:
    输入:[[1,1,1],[1,2,2],[1,3,2],[2,1],[1,4,4],[2,2]],3
    输出:[1,-1]
    

    其实我审题没明白,看了高赞回答,才懂

    二维数组中第0位即:[1,1,1],第一个1表示opt=1,要set(1,1),即要将(1,1)插入缓存
    二维数组中第1为即:[1,2,2],第一个1表示opt=1,要set(2,2),即要将(2,2)插入缓存
    二维数组中第2位即:[1,3,2],第一个1表示opt=1,要set(3,2),即要将(3,2)插入缓存
    二维数组中第3位即:[2,1],第一个2表示opt=2,要get(1),即从缓存中查找key为1的值,前面已经插入了key=1,所以返回1,这个要保存到返回数组中
    二维数组中第5位即:[2,2],第一个2表示opt=2,要get(2),即从缓存中查找key为2的值,由于缓存大小为3,所以前面插入的key=2已经被挤出缓存,所以返回结果为-1,这个要保存到返回数组中
    所以输出为[1,-1]
    示例1中,输入最后一个单独的3表示缓存大小
    

    我的代码

    class Solution:
        def LRU(operators , k ):
            # write code here
            class LRU_cache:
                def __init__(self,k):
                    self.lenth=k
                    self.present=0
                    self.cache=dict()
                    self.quque=[]
                    self.output=[]
                    
                def _set(self,l):
                    #修改quque和cache
                    key=l[0]
                    val=l[1]
                    self.cache[key]=val
                    
                    if key in self.quque:
                        self.quque.remove(key)
                    self.quque=[key]+self.quque
                    
                    if len(self.quque)>self.lenth:
                        self.cache.pop(self.quque[-1])
                        self.quque=self.quque[:-1]
                        
                    print('set一次',self.cache,self.quque)
                    
                def _get(self,key):
                    if key in self.cache.keys():
                        self.output.append(self.cache.get(key))
                        self.quque.remove(key)
                        self.quque=[key]+self.quque
                    else:
                        self.output.append(-1)
                    
                    print('get一次',self.cache,self.quque)
                
            cache=LRU_cache(k)
            for l in operators:
                if l[0]==1:
                    cache._set(l[1:])
                else:
                    cache._get(l[1])
                
                print('操作一次',cache.cache,cache.quque)
            
            return(cache.output)
    

    ··········第二次尝试 ROUND 2··········

    第一版的算法是用一个list模拟了这个cache,在增、查操作的时候都需要遍历这个列表,时间复杂度为O(n),不是常数级。
    看了答案解析,要实现O(1)的时间开销,需要使用哈希表数据结构。在python中,字典dict便是哈希结构。在两个操作中对数据进行读写时,通过哈希索引快速定位。在移动键值数据时,链表结构才能满足O(1)的时间需求,只需要断掉当前链接,再在指定位置链接进去即可。
    答案解析给出的方式是哈希表双向链表的结构。
    参考代码如下

    标准答案
    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]
            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
                # 添加至双向链表的头部
                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
    
    作者:LeetCode-Solution
    链接:https://leetcode-cn.com/problems/lru-cache/solution/lruhuan-cun-ji-zhi-by-leetcode-solution/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    

    以及一个利用python原生数据结构collections.OrderedDict取巧的答案

    class LRUCache(collections.OrderedDict):
    
        def __init__(self, capacity: int):
            super().__init__()
            self.capacity = capacity
    
    
        def get(self, key: int) -> int:
            if key not in self:
                return -1
            self.move_to_end(key)
            return self[key]
    
        def put(self, key: int, value: int) -> None:
            if key in self:
                self.move_to_end(key)
            self[key] = value
            if len(self) > self.capacity:
                self.popitem(last=False)
    
    作者:LeetCode-Solution
    链接:https://leetcode-cn.com/problems/lru-cache/solution/lruhuan-cun-ji-zhi-by-leetcode-solution/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    

    那么在参考了解题思路以后,我又编写了一次程序,但是此时的我并没有理解为什么要将双向链表头尾相连成循环链表,觉得循环链表在尾部丢弃操作会带来意想不到的问题。于是本人第二次代码如下:

    class Solution:
        def LRU(operators , k ):
            # write code here
            
            class DataNode:
                def __init__(self,l:list):
                    self.key=l[0]
                    self.value=l[1]
                    self.pri=None
                    self.beh=None
                    
                    
                    
            class LRU_cache:
                def __init__(self,k):
                    self.length=k
                    self.present=None
                    self.last=None
                    self.head=None
                    self.cache=dict()
                    
                def _set(self,l:list):
                    key=l[0]
                    value=l[1]
                    
                    if len(self.cache)==0:
                        node=DataNode(l)
                        self.cache[key]=node
                        self.head=node
                        self.last=node
                    elif key in self.cache.keys():
                        node=self.cache[key]
                        node.value=value
                        
                        if self.last.key==key and self.last.key!=self.head.key:
                            self.last=node.beh
                            self.last.pri=None
                            node.pri=self.head
                            self.head.beh=node
                            self.head=node
                            node.beh=None
                        elif self.head.key==key and self.last.key!=self.head.key:
                            pass
                        elif len(self.cache)==1:
                            pass
                        else:
                            node.pri.beh=node.beh
                            node.beh.pri=node.pre
                            node.pri=self.head
                            self.head=node
                            node.beh=None
                    elif key not in self.cache.keys() and len(self.cache)==self.length:
                        node=DataNode(l)
                        #丢出最后一个节点
                        self.cache.pop(self.last.key)
                        self.last=self.last.beh
                        self.last.pri=None
                        #插进新的节点
                        self.cache[key]=node
                        node.pri=self.head
                        self.head.beh=node
                        self.head=node
                        self.head.beh=None
                    else:
                        #变头不变尾
                        node=DataNode(l)
                        self.cache[key]=node
                        node.pri=self.head
                        self.head.beh=node
                        self.head=node
                        self.head.beh=None
                        
                def _get(self,key) ->int:
                    if key in self.cache.keys():
                        #该节点调到开头
                        node=self.cache[key]
                        
                        if self.last.key==key and self.last.key!=self.head.key:
                            self.last=node.beh
                            self.last.pri=None
                            node.pri=self.head
                            self.head.beh=node
                            self.head=node
                            node.beh=None
                        elif self.head.key==key and self.last.key!=self.head.key:
                            pass
                        elif len(self.cache)==1:
                            pass
                        else:
                            node.pri.beh=node.beh
                            node.beh.pri=node.pre
                            node.pri=self.head
                            self.head=node
                            node.beh=None
                            
                        return(node.value)
                        
                    else:
                        return(-1)
                        
    
            output=[]
            cache=LRU_cache(k)
    
            for l in operators:
                if l[0]==1:
                    cache._set(l[1:])
                else:
                    output.append(cache._get(l[1]))
    
            return(output)
    
    a=Solution
    print(a.LRU([[1,1,1],[1,2,2],[1,3,2],[2,1],[1,4,4],[2,2]],3))               
    

    在编写过程中,我就意识到为什么要使用循环链表:
    在插入和读取操作中,每当涉及到节点激活成最新使用节点时,不循环链表都要考虑

    • 被移动的节点在链表前端
    • 被移动的节点在链表后端
    • 被移动的节点在链表中间
      这三种情况,相当痛苦。if...else...已经把自己绕晕了。而且我脑内模拟了一下,循环链表不会在删除最后一个节点时带来麻烦。

    那第二次提交的结果如何呢?
    答:例子正确输出了,但在测试其他输入时候报了节点没有前一节点的错,不用说,肯定是前面所说的不循环链表端点处理除了问题。
    我也有准备debug一下,但是牛客IDE里没发考出完整的输入,加上我上述代码疯狂的if-else已经成为了传说中的‘屎山’,看的我头疼,第二次挑战就此作罢。准备第三次挑战,这次要改进的地方是链表改成循环链表。

  • 相关阅读:
    管理心理学[9095]
    汽车文化[1196]
    小四轴——空心杯电机引起的电源干扰
    38 时序电路扩展2
    37 时序电路扩展1
    36 时序电路的动态特性分析2
    35 时序电路的动态特性分析1
    34 同步时序电路的设计方法2
    33 同步时序电路的设计方法1
    60. 第k个排列
  • 原文地址:https://www.cnblogs.com/kang-mei-208/p/14677895.html
Copyright © 2020-2023  润新知