• 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

    思路:要想实现get与put的O(1)时间复杂度,需要将哈希表双向链表组合使用。

    哈希表查找一个元素的时间复杂度是O(1),但哈希表内数据无序存放,体现不出最近最少使用的顺序关系;而链表插入和删除一个元素的时间复杂度是O(1),查找的时间复杂度是O(n),不过链表存储的数据是有序的。

    注意我们实现的双向链表只能从头部插入,也就是说靠头部的数据是最近使用的,靠尾部的数据是最久未使用的

    为什么要使用双向链表?因为用单链表删除一个结点时,需要从头遍历,找到待删除结点的前驱结点,所以采用双向链表,可以直接找到这个前驱结点。

    为什么双向链表的结点既要存key又要存val?因为删除链表尾部结点时返回删除了的结点,需要通过这个结点的key值,删除map中的key。

    算法就像搭乐高:带你手撸 LRU 算法 :: labuladong的算法小抄 (gitee.io) 

    1. struct DLinkedNode//双向链表结点类,存储前驱、后继结点位置,存储key、value
    2. {
    3. int key;
    4. int val;
    5. DLinkedNode* prev;
    6. DLinkedNode* next;
    7. DLinkedNode():key(0),val(0),prev(nullptr),next(nullptr){}
    8. DLinkedNode(int key,int val):key(key),val(val),prev(nullptr),next(nullptr){}
    9. };
    10. class LRUCache {
    11. private:
    12. unordered_map<int,DLinkedNode*> mapping;//map(key,&DLinkedNode(key,val))
    13. DLinkedNode* head;
    14. DLinkedNode* tail;
    15. int size;
    16. int capacity;
    17. public:
    18. LRUCache(int capacity):capacity(capacity),size(0) {
    19. head=new DLinkedNode(0,0);
    20. tail=new DLinkedNode(0,0);
    21. head->next=tail;
    22. tail->prev=head;
    23. }
    24. int get(int key) {
    25. if(mapping.count(key))//key在哈希表中,因为刚刚使用过,所以提到表头
    26. {
    27. DLinkedNode* node=mapping[key];
    28. moveToHead(node);
    29. return node->val;
    30. }
    31. else {
    32. return -1;
    33. }
    34. }
    35. void put(int key, int value)
    36. {
    37. if(mapping.count(key))//key已经在哈希表中,更新val值,提至表头
    38. {
    39. DLinkedNode* node=mapping[key];
    40. node->val=value;
    41. moveToHead(node);
    42. }
    43. else//key不在哈希表中
    44. {
    45. DLinkedNode* node=new DLinkedNode(key,value);
    46. addToHead(node);
    47. mapping[key]=node;
    48. size++;
    49. if(size>capacity)//缓存满了,删除链表尾部结点,删除map中的key
    50. {
    51. DLinkedNode* removed= removeTail();
    52. mapping.erase(removed->key);
    53. size--;
    54. }
    55. }
    56. }
    57. void addToHead(DLinkedNode* node)//头插法插入一个结点
    58. {
    59. node->next=head->next;
    60. head->next->prev=node;
    61. node->prev=head;
    62. head->next=node;
    63. }
    64. void removeNode(DLinkedNode* node)//删除一个结点
    65. {
    66. node->prev->next=node->next;
    67. node->next->prev=node->prev;
    68. }
    69. void moveToHead(DLinkedNode* node)//把一个结点提至链表头部
    70. {
    71. removeNode(node);
    72. addToHead(node);
    73. }
    74. DLinkedNode* removeTail()//删除链表尾部的一个结点
    75. {
    76. DLinkedNode* node=tail->prev;
    77. removeNode(node);
    78. return node;
    79. }
    80. };

     

  • 相关阅读:
    在一台电脑上安装多个python版本(小白教程)
    【开源】基于Vue.js的音乐偏好度推荐系统的设计和实现
    24.绳子切割
    Sublime Text 4 for Mac激活下载
    文件包含漏洞
    【springboot整合ES】springboot整合ES
    DP58 红和蓝
    机器人过程自动化(RPA)入门 4. 数据处理
    C++ Reference: Standard C++ Library reference: Containers: deque: deque: swap
    Java21的新特性
  • 原文地址:https://blog.csdn.net/weixin_50437588/article/details/126332273