leetcode146. LRU 缓存【python3哈希表+双向链表】利用OrderedDict以及自实现双向链表

题目:

请你设计并实现一个满足 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); // 返回 1lRUCache.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); // 返回 3lRUCache.get(4); // 返回 4

标题题解

该题目的核心主要是get与put函数。get函数的要求非常简单就是通过key获取相应的value,hash表即可满足题目要求。put函数主要超过capacity的时候需要把最久未使用的关键字删除,再添加新的key-value。这里显然需要对key进行排序,自然想到的就是Python3中collections中的OrderedDict。代码如下:

class LRUCache:

    def __init__(self, capacity: int):
        #定义一个dict
        self.capacity=capacity
        #定义一个有序字典
        self.LRU_dic=collections.OrderedDict()
        #使用一个变量记录dic的数据量
        self.used_num=0

    def get(self, key: int) -> int:
        if key in self.LRU_dic:
            #如果被使用了利用move_to_end移动到队尾,保证了LRU_dic是按使用顺序排序的
            self.LRU_dic.move_to_end(key)
            return self.LRU_dic.get(key)
        else:
            return -1

    def put(self, key: int, value: int) -> None:
        if key in self.LRU_dic:         
            self.LRU_dic[key]=value
            self.LRU_dic.move_to_end(key)
        else:
            #判断当前已有数量是否超过capacity
            if self.used_num==self.capacity:
                #删除首个key,因为首个key是最久未使用的
                self.LRU_dic.popitem(last=False)
                self.used_num-=1
            self.LRU_dic[key]=value
            self.used_num+=1

然后,直接调用包通常不是面试的时候考察的主要能力。因此需要根据一些数据结构来实现OrderedDict类似的功能。
可以发现哈希表肯定是需要的,能存储key,value相关信息。这里一个主要的需求是一个序的数据结构能在头部与尾部都实现O(1)时间复杂度的操作。list也能满足这个要求,但是list对于元素的查询、更新以及有排序操作的支持不是很友好。显然这里使用双向链表比较合适。下面详细介绍一下哈希表与双向链表。数据结构清楚了实现就比较容易了。
先介绍下双向链表。
image.png
pre表示前向指针,key,value就是键值,next表示下一步指针指的节点。初始化只有head与tail两个节点,这里把新使用的节点放到链表尾部,头部链表节点就是最久未使用的节点。
leetcode146. LRU 缓存【python3哈希表+双向链表】利用OrderedDict以及自实现双向链表_第1张图片
因为使用过的key需要把相应的链表节点移动到队尾,因此需要实现类似OrderedDict的move_to_end这个函数的功能。如果超出capacity的时候要删除最久未使用的节点也就是删除head.next节点,也需要实现一个表头节点删除的功能。定义完链表,hash表就好实现了key,对应给定的key,value对应的是双向链表中的节点。这样通过key就能在O(1)时间定位到双向链表中的节点。代码实现如下:

class LRUCache:
    class D_link:
        def __init__(self,key=0,value=0):
            self.key=key
            self.value=value
            self.pre=None
            self.next=None


    def __init__(self, capacity: int):
        #定义一个dict
        self.capacity=capacity
        self.LRU_dic={}
        #使用一个变量记录dic的数据量
        self.used_num=0
        #初始化双向链表
        self.head=LRUCache.D_link()
        self.tail=LRUCache.D_link()
        self.head.next=self.tail
        self.tail.pre=self.head

    def get(self, key: int) -> int:
        if key in self.LRU_dic:
            node=self.LRU_dic.get(key)
            #因为访问过需要移动到链表尾
            self.move_to_end(node)
            return node.value
        else:
            return -1

    def put(self, key: int, value: int) -> None:
        if key in self.LRU_dic:
            #更新LRU_dic的value         
            node= self.LRU_dic[key]
            node.value=value
            self.move_to_end(node)
        else:
            #判断当前已有数量是否超过capacity
            if self.used_num==self.capacity:
                #删除双链表最前面的Node
                del_key=self.del_head()
                self.used_num-=1
                #从LRU_dic中删除Key
                del self.LRU_dic[del_key]
            new_node=LRUCache.D_link(key,value)
            #在链表尾部插入
            self.insert_node(new_node)
            self.LRU_dic[key]=new_node
            self.used_num+=1
    
    def move_to_end(self,node):
        #首先把node取出来
        pre_node=node.pre
        post_node=node.next
        pre_node.next=post_node
        post_node.pre=pre_node
        tail_pre=self.tail.pre
        tail_pre.next=node
        node.pre=tail_pre
        node.next=self.tail
        self.tail.pre=node

    def del_head(self):
        key=self.head.next.key
        post_node=self.head.next.next
        self.head.next=post_node
        post_node.pre=self.head
        return key

    def insert_node(self,node):
        pre_tail=self.tail.pre
        pre_tail.next=node
        node.pre=pre_tail
        node.next=self.tail
        self.tail.pre=node

计算复杂度

  • 时间复杂度,题目要求就是 O ( 1 ) O(1) O(1),这里也是 O ( 1 ) O(1) O(1)
  • 空间复杂度,因为链表长度要求是在capacity下的,因此空间复杂度为 O ( c a p a c i t y ) O(capacity) O(capacity)

你可能感兴趣的:(算法学习笔记,链表,缓存,散列表)