当前位置:   article > 正文

LeetCode LRU缓存_请你设计并实现一个满足 lru

请你设计并实现一个满足 lru

146. LRU 缓存

请你设计并实现一个满足  LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

  • LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

示例:

输入

["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]

[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]

输出

[null, null, null, 1, null, -1, null, -1, 3, 4]

解释

LRUCache lRUCache = new LRUCache(2);

lRUCache.put(1, 1); // 缓存是 {1=1}

lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}

lRUCache.get(1);    // 返回 1

lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}

lRUCache.get(2);    // 返回 -1 (未找到)

lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}

lRUCache.get(1);    // 返回 -1 (未找到)

lRUCache.get(3);    // 返回 3

lRUCache.get(4);    // 返回 4

思路:

  LRU缓存也算是比较高频和经典的题目了,尤其是后端选手很容易遇到这个题(不过我这里是用python来实现的)。对于LRU主要就是定义一个get和一个put的接口,然后每次操作到相关的数据之后,就需要把该数据更新到面板最前面。

  首先直接说实现方式和结论:为了使get和put的平均时间复杂度都为O(1),我们需要使用个双向链表一个哈希表

  根据题意,每次存入的数据都是key-value的形式,因此对于链表中的每一个节点,内部至少有两个变量:key和value,其次因为它是双向链表,我们要需要pre指针和next指针;而哈希表的使用则是这样的:哈希表中的key就是存入数据的key,哈希表中的value就是节点,直观点可以这么理解:哈希表[3]=Node(3,5),即int型对应Node型。

  因此节点的定义方式是这样的:

  class ListNode():#里面有四个变量

    def __init__(self,key=None,value=None):

        self.key = key

        self.value=value

        #还有两个指针,创建了但是没值,就等于None吧

        self.pre=None

        self.next=None

  整个LRUCache的定义方式是这样的:

class LRUCache():

    def __init__(self,capacity): #一开始除了头尾节点外,长度为0,头尾互相指

        self.head=ListNode()#头结点

        self.tail=ListNode()#尾节点

        self.head.next=self.tail#头节点的next初始化指向尾

        self.tail.pre=self.head#为节点的pre初始化指向头

        self.hashmap={} #双向链表有了,再定义一个哈希表

        self.capacity=capacity#第四个参数 容量

  数据结构弄清了,接下来就是put和get的算法部分。

  在说get和put之前,先要弄清一个事情,LRU的一大特点就是在操作某个数据之后,需要我们把某个数据移动到“最上面”,表示刚刚操作过。因此后面不管是get还是put都会有这个需求,我们可以提前定义出来。目前我们的双向链表头结点是head,尾节点是tail,这两个变量我们不会用来存放实际数据,只是用于插入和取出真正的首位数据。

  注意:我这里默认越往右的数据越新,越往左的数据越老。

  比如现在我们要把数据移到LRU的“最上面”、“最新处”,我们把这个功能叫做move_to_first,而这个first的位置是在最右边,也就是tail之前的那个节点。而最老的节点在哪呢?在head的后面

  知道了first的位置,我们定义起这个功能就很简单了:

def move_to_first(self,key):

        node = self.hashmap[key] #把node从当前双向链表取出

        #取出时让原来的左右两边相连,不要断开

        node.pre.next=node.next

        node.next.pre=node.pre

        #把node移动到最后当作first (严格说其实是tail的前面一位,head,tail始终首尾)

        #双向链表插入节点方式:先建立新链接(前两句)再断开和改变原链接(后两句)

        node.next=self.tail

        node.pre = self.tail.pre

        node.next.pre=node

        node.pre.next=node

  现在我们可以正式开始看get和put了。

  首先是get,这个很简单,通过哈希表来进行get(key)的查找,复杂度就是O(1),如果查不到则按提议返回-1,若查到了则返回这个Node的value值,并且因为get过这个数字,需要把这个节点move_to_first。

  其次是put,put是往链表里添加节点了,会涉及到容量capacity的问题,所以会麻烦一些。Put的总体逻辑是这样的:首先判断要添加的节点的key在不在哈希表中,如果在的话,我们不需要添加新节点,只需要更新value值即可,并且将其move_to_first。如果不在哈希表中,我们要先去判断容量是否足够,如果容量足够,则我们直接在first(tail之前)的位置插入这个新节点;如果容量不够,则需要先删除最末尾(head之后)的节点,空出一个位置后,再在first位置插入这个新节点。

代码:

  1. # class LRUCache:
  2. #     def __init__(self, capacity: int):
  3. #     def get(self, key: int) -> int:
  4. #     def put(self, key: int, value: int) -> None:
  5. class ListNode():#里面有四个变量
  6.     def __init__(self,key=None,value=None):
  7.         self.key = key
  8.         self.value=value
  9.         #还有两个指针,创建了但是没值,就等于None吧
  10.         self.pre=None
  11.         self.next=None
  12. class LRUCache():
  13.     def __init__(self,capacity): #一开始除了头尾节点外,长度为0,头尾互相指
  14.         self.head=ListNode()#头结点
  15.         self.tail=ListNode()#尾节点
  16.         self.head.next=self.tail#头节点的next初始化指向尾
  17.         self.tail.pre=self.head#为节点的pre初始化指向头
  18.         #双向链表有了,再定义一个哈希表
  19. self.hashmap={}
  20.         #第四个参数 容量
  21.         self.capacity=capacity
  22.     def move_to_first(self,key):
  23.         node = self.hashmap[key] #把node从当前双向链表取出
  24.         #取出时让原来的左右两边相连,不要断开
  25.         node.pre.next=node.next
  26.         node.next.pre=node.pre
  27.         #把node移动到最后当作first (严格说其实是tail的前面一位,head,tail始终首尾)
  28.         #双向链表插入节点方式:先建立新链接(前两句)再断开和改变原链接(后两句)
  29.         node.next=self.tail
  30.         node.pre = self.tail.pre
  31.         node.next.pre=node
  32.         node.pre.next=node
  33.     #按要求写get方法,传入一个key
  34.     def get(self,key):
  35.         #判断有无
  36.         if key not in self.hashmap:
  37.             return -1#无则直接返回-1
  38.         #如果有,调用move_to_first更新一波
  39.         self.move_to_first(key)
  40.         node = self.hashmap[key]#通过哈希表取出节点
  41.         return node.value#通过节点取出值
  42.     #put方法,传入key和value两个值
  43. def put(self,key,value):
  44.         #首先判断key是不是已经在hash表中了
  45.         if key in self.hashmap:
  46.             node = self.hashmap[key]
  47.             node.value=value#有则更新值
  48.             #再移至第first
  49.             self.move_to_first(key)
  50.         else:#不在hash表里
  51.             #判断够不够装下
  52.             if len(self.hashmap)==self.capacity:#已经满了
  53.                 #删除掉head后面的节点
  54.                 #既要删除节点 还要删除哈希表中的映射!!!不要漏写
  55.                 delete_key = self.head.next.key#定位到要删除的key
  56.                 self.hashmap.pop(delete_key)#哈希表删除映射,这里容易漏
  57.                 #删除这个节点
  58.                 self.head.next = self.head.next.next
  59.                 self.head.next.pre = self.head
  60.             #插入这个新节点
  61.             node = ListNode(key,value)#new一个节点
  62.             self.hashmap[key]=node#建立映射
  63.             #双向链表插入节点方式:
  64.             #先建立新链接(前两句)再断开和改变原链接(后两句)
  65.             node.next=self.tail
  66.             node.pre=self.tail.pre
  67.             node.next.pre = node
  68.             node.pre.next=node

小结:

  LRU机制首先得知道它是做什么的,清楚它最基本的机制(好像就类似于手机后台的缓存一样,刚刚用过的会出现在最上面,很久不用的在最底部直至被淘汰挤没)。然后要记住满足题目复杂度的实现方法:使用双向链表+哈希表。此外把尝试用的功能move_to_first提前定义出来,方便put和get等地方的调用。最后要注意一些细节,put 的时候先查找是否已存在,插入的时候考虑容量等。

  总体来说LRU看起来步骤挺复杂的但是逻辑很清楚,不过能不出错地一遍写出来也不是很容易。写的时候要注意细节,一定要自己亲自写一下才知道哪些地方要注意。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/491689
推荐阅读
相关标签
  

闽ICP备14008679号