LeetCode (力锋) 146 questions : mécanisme de cache LRU - table de hachage + solution de liste à double liaison avec notes détaillées

题目描述

A partir des structures de données que vous maîtrisez , concevez et implémentez un mécanisme de cache LRU (moins récemment utilisé).

Implémentez la classe LRUCache :

  • LRUCache(int capacity) Initialise le cache LRU avec un entier positif comme capacité de capacité
  • int get(int key) Si le mot clé key existe dans le cache, renvoie la valeur de la clé, sinon renvoie -1
  • void put(int key, int value) Si le mot-clé existe déjà, modifiez sa valeur de données ; si le mot-clé n'existe pas, insérez l'ensemble de "keyword-value" . Lorsque la capacité du cache atteint sa limite supérieure, il convient de supprimer les plus anciennes valeurs de données inutilisées avant d'écrire de nouvelles données, faisant ainsi de la place pour de nouvelles valeurs de données .

示例

输入
["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

思路分析

Dans la description du sujet, permettez-moi de concevoir et d'implémenter un mécanisme de cache LRU avec ma propre structure de données. Ma première réaction est d'utiliser une table de hachage et de compléter la méthode "mettre, obtenir" du mécanisme de cache LRU en ajoutant, supprimant , vérifier et modifier la table de hachage. Au début, j'ai pensé à définir une table de hachage pour stocker le nombre de fois que chaque élément a été utilisé pour déterminer quel élément doit être supprimé, mais j'ai découvert plus tard que ce n'était pas possible, donc il ne peut pas représenter l'utilisation des éléments dans le mécanisme de cache ( problème de séquence ) , puisque le problème de séquence est impliqué, la séquence la plus réfléchissante dans la structure de données devrait être la liste chaînée. Ici, nous utilisons une liste chaînée bidirectionnelle, mais je pense toujours qu'une liste liée à sens unique devrait également fonctionner, mais je ne l'ai pas essayé. Tout le monde est intéressé Vous pouvez l'essayer, je vous attendrai dans la zone de commentaires. Permettez-moi de parler de la logique générale de l'idée de résolution de problèmes.
Dans le procédé d'initialisation, une liste à double liaison avec deux nœuds virtuels et une table de hachage est définie, et la capacité du cache LRU et la taille actuelle du cache sont spécifiées.
L'étape suivante consiste à ajouter des éléments. Si l'élément est contenu dans le cache actuel, il vous suffit de rechercher l'élément et de modifier sa valeur ;
si l'élément n'est pas contenu dans le cache actuel, déterminez d'abord si le cache actuel a atteint la Si la limite supérieure n'est pas atteinte, alors ajoutez-la directement et déplacez le nœud au début de la liste doublement chaînée ( ici, placez l'élément le plus récemment opéré au début de la liste doublement chaînée, de sorte que les éléments qui n'ont pas été exploités depuis longtemps seront classés à la fin de la liste doublement liée ); Si la limite supérieure est atteinte, vous devez supprimer l'élément de queue de la liste doublement liée, le supprimer dans le cache, puis ajouter un nouvel élément, et déplacez le nouvel élément vers la tête de la liste doublement liée.
La dernière est la méthode de récupération de l'élément. Si l'élément à récupérer n'est pas dans le cache, il retournera directement -1 ; si l'élément à récupérer est dans le cache, il retournera directement la valeur de l'élément.

Note : La chose la plus ingénieuse dans ce sujet est d'utiliser une liste doublement chaînée + table de hachage. Chaque nœud de la liste doublement chaînée stocke des éléments . La position à la fin, et ceux qui n'ont pas été opérés récemment sont placés à la fin ), afin que nous puissions supprimer l'élément qui est actuellement le moins utilisé ( l'élément de fin de la liste doublement liée ). La relation de mappage entre la clé et la valeur est conservée dans le cache cache.

Parlons d'abord de l'idée et de la logique, examinons l'implémentation spécifique du code :

代码

# 双向链表的类定义
class dual_linkList_node:
    def __init__(self, key=0, val=0):
        self.key = key
        self.val = val
        self.prev = None
        self.next = None
# LRU缓存机制的类定义
class LRUCache:
	# LRU缓存机制的初始化方法
    def __init__(self, capacity: int):
        self.cache = dict() # 初始化存储映射关系的字典
        self.head = dual_linkList_node() # 双向链表的头节点
        self.tail = dual_linkList_node() # 双向链表的尾节点
        self.head.next = self.tail # 头节点指向尾节点
        self.tail.prev = self.head # 尾节点指向头节点, 至此双向链表构建完成
        self.capacity = capacity # 初始化LRUCache的容量大小
        self.size = 0
    # 在这里将元素储存在双向链表中, 映射关系存在字典里 #
    def get(self, key: int) -> int: # 获取LRUCache中的某个元素
        if key in self.cache: # 若待取元素在cache内, 则直接根据key取出该节点, 再将该节点移动到双向链表的头部, 并返回该元素的值
            node = self.cache[key]
            self.move_to_head(node)
            return node.val
        else: # 如果待取元素不在cache内, 返回 -1
            return -1

    # 删除双向链表的一个元素
    def remove_node(self, node):
        node.prev.next = node.next # node的前一个节点向后指向node后一个节点
        node.next.prev = node.prev # node的后一个节点向前指向node前一个节点

    # 在head节点之后添加node
    def add_to_head(self, node):
        # 在双向链表中, node和self.head都需要进行变换
        node.prev = self.head
        node.next = self.head.next
        self.head.next.prev = node
        self.head.next = node

    # node节点移动到双向链表头节点之后
    def move_to_head(self, node):
        self.remove_node(node) # 先在双向链表中移除该node节点
        self.add_to_head(node) # 再将该node节点添加在head之后

    # 移除双向链表尾节点
    def remove_tail(self):
    	# 因为头节点和尾节点都为虚拟节点,因此删除尾节点即删除虚拟尾节点的前一个节点
        node = self.tail.prev
        self.remove_node(node)
        return node

    # 添加元素
    def put(self, key: int, value: int) -> None:
        if key not in self.cache:
            # 如果 key 不存在,创建一个新的节点
            node = dual_linkList_node(key, value)
            # 添加进哈希表
            self.cache[key] = node
            # 添加至双向链表的头部
            self.add_to_head(node)
            self.size += 1
            if self.size > self.capacity:
                # 如果超出容量,删除双向链表的尾部节点
                removed = self.remove_tail()
                # 删除哈希表中对应的项
                self.cache.pop(removed.key)
                self.size -= 1
        else:
        	# 如果带输入的元素存在于cache,则只需取出存储该元素的节点,改变其值并移动到双向链表头节点的位置即可
            node = self.cache[key]
            node.val = value
            self.move_to_head(node)

运行结果

insérez la description de l'image ici

Je suppose que tu aimes

Origine blog.csdn.net/Just_do_myself/article/details/118499267
conseillé
Classement