• 【LeetCode】LRU 缓存 [M](LRU内存替换算法)


    146. LRU 缓存 - 力扣(LeetCode)

    一、题目

    请你设计并实现一个满足  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

    提示:

    • 1 <= capacity <= 3000
    • 0 <= key <= 10000
    • 0 <= value <= 105
    • 最多调用 2 * 105 次 get 和 put

    二、代码

    1. class LRUCache {
    2. public MyCache cache;
    3. public LRUCache(int capacity) {
    4. cache = new MyCache(capacity);
    5. }
    6. public int get(int key) {
    7. return cache.get(key);
    8. }
    9. public void put(int key, int value) {
    10. cache.put(key, value);
    11. }
    12. // 双端链表节点类
    13. public static class Node {
    14. public Integer key;
    15. public Integer value;
    16. public Node next;
    17. public Node last;
    18. public Node(Integer key, Integer value) {
    19. this.key = key;
    20. this.value = value;
    21. }
    22. }
    23. // 双端链表类
    24. public static class NodeLinkedList {
    25. // 头尾指针直接指向头节点和尾节点 head为空说明链表为空
    26. public Node head;
    27. public Node tail;
    28. // 初始化
    29. public NodeLinkedList() {
    30. this.head = null;
    31. this.tail = null;
    32. }
    33. // 将node节点加到链表尾部
    34. public void add(Node node) {
    35. // node为空直接返回
    36. if (node == null) {
    37. return;
    38. }
    39. // head为空说明此时链表为空
    40. if (head == null) {
    41. // 直接将头尾指针指向这个节点
    42. head = node;
    43. tail = node;
    44. // 链表不为空
    45. } else {
    46. // 查到链表尾部
    47. tail.next = node;
    48. node.last = tail;
    49. tail = node;
    50. }
    51. }
    52. // 移除头节点
    53. public void remove() {
    54. // 链表存在同节点
    55. if (head != null) {
    56. // 链表只有一个节点
    57. if (head == tail) {
    58. head = null;
    59. tail = null;
    60. // 链表有多个节点
    61. } else {
    62. head.next.last = null;
    63. head = head.next;
    64. }
    65. }
    66. }
    67. // 将node节点移动到尾部
    68. public void moveNodeToTail(Node node) {
    69. // 如果该节点就是尾节点,直接返回
    70. if (tail == node) {
    71. return;
    72. }
    73. // 链表不为空
    74. if (head != null) {
    75. // node不是头节点
    76. if (node != head) {
    77. // 将node从链表原位置分离
    78. node.last.next = node.next;
    79. node.next.last = node.last;
    80. // node是头节点
    81. } else {
    82. // 将node从链表原位置分离
    83. head = head.next;
    84. head.last = null;
    85. }
    86. // 将node移动到尾部
    87. tail.next = node;
    88. node.last = tail;
    89. node.next = null;
    90. tail = node;
    91. }
    92. }
    93. }
    94. // 缓存类
    95. public static class MyCache {
    96. // 记录当前链表有哪些节点
    97. public HashMap cacheMap;
    98. // 双端链表
    99. public NodeLinkedList nodeLinkedList;
    100. // 缓存大小
    101. public int capacity;
    102. // 初始化
    103. public MyCache(int capacity) {
    104. this.cacheMap = new HashMap<>();
    105. this.nodeLinkedList = new NodeLinkedList();
    106. this.capacity = capacity;
    107. }
    108. // 获取指定数据
    109. public int get(int key) {
    110. Node node = cacheMap.get(key);
    111. if (node != null) {
    112. // 因为对该数据操作了,将其移动到尾部
    113. nodeLinkedList.moveNodeToTail(node);
    114. return node.value;
    115. }
    116. // 没有该数据返回-1
    117. return -1;
    118. }
    119. // 将数据加入到缓存 如果已存在就更新
    120. public void put(int key, int value) {
    121. Node node = cacheMap.get(key);
    122. // 修改已存在的数据
    123. if (node != null) {
    124. node.value = value;
    125. // 因为对该数据操作了,将其移动到尾部
    126. nodeLinkedList.moveNodeToTail(node);
    127. // 该数据是新加入的
    128. } else {
    129. // 为其创建node
    130. node = new Node(key, value);
    131. // 缓存还没有满
    132. if (cacheMap.size() < capacity) {
    133. // 直接加到尾部
    134. nodeLinkedList.add(node);
    135. // 加入到Map
    136. cacheMap.put(key, node);
    137. } else {
    138. // 将链表头移除,他是最久没有操作过的数据
    139. // 从Map移除
    140. cacheMap.remove(nodeLinkedList.head.key);
    141. // 从链表移除
    142. nodeLinkedList.remove();
    143. // 将新数据加入
    144. nodeLinkedList.add(node);
    145. cacheMap.put(key, node);
    146. }
    147. }
    148. }
    149. }
    150. }
    151. /**
    152. * Your LRUCache object will be instantiated and called as such:
    153. * LRUCache obj = new LRUCache(capacity);
    154. * int param_1 = obj.get(key);
    155. * obj.put(key,value);
    156. */

    三、解题思路 

    双向链表 + 哈希表实现

    Hash表里:

    key:用来唯一标识双向链表里的Node,例如A

    value:Key对应的节点Node (A, 3),一个双向链表的节点,有last、next指针

    用双向链表来表示谁是较近操作的,谁是较远操作的。

    越靠近双向链表的尾巴越近,越靠近双向链表的头部越远。

    双向链表记一个头指针,记一个尾指针,可以很方便的把一个数据直接挂在尾巴上。

  • 相关阅读:
    InVideo AI:用人工智能轻松制作视频
    SQL行列转换
    无代码编程时代的到来:新兴工具和平台的前瞻展望
    VScode保存自动格式化
    一款超好用的神器Apifox,甩 Swagger 几条街...(荣耀典藏版)
    JavaS
    Netty 学习(七):NioEventLoop 对应线程的创建和启动源码说明
    String类重点知识思维导图
    leetcode 698. 划分为k个相等的子集
    [PAT练级笔记] 03 Basic Level 1004
  • 原文地址:https://blog.csdn.net/cy973071263/article/details/127902634