1. //运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
    2. //
    3. //
    4. //
    5. // 实现 LRUCache 类:
    6. //
    7. //
    8. // LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
    9. // int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
    10. // void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上
    11. //限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
    12. //
    13. //
    14. //
    15. //
    16. //
    17. //
    18. // 进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?
    19. //
    20. //
    21. //
    22. // 示例:
    23. //
    24. //
    25. //输入
    26. //["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
    27. //[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
    28. //输出
    29. //[null, null, null, 1, null, -1, null, -1, 3, 4]
    30. //
    31. //解释
    32. //LRUCache lRUCache = new LRUCache(2);
    33. //lRUCache.put(1, 1); // 缓存是 {1=1}
    34. //lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
    35. //lRUCache.get(1); // 返回 1
    36. //lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
    37. //lRUCache.get(2); // 返回 -1 (未找到)
    38. //lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
    39. //lRUCache.get(1); // 返回 -1 (未找到)
    40. //lRUCache.get(3); // 返回 3
    41. //lRUCache.get(4); // 返回 4
    42. //
    43. //
    44. //
    45. //
    46. // 提示:
    47. //
    48. //
    49. // 1 <= capacity <= 3000
    50. // 0 <= key <= 3000
    51. // 0 <= value <= 104
    52. // 最多调用 3 * 104 次 get 和 put
    53. //
    54. // Related Topics 设计
    55. // 👍 1290 👎 0

    LRU机制,第一次听说是在Redis的内存淘汰策略。
    最远未使用到的节点会被淘汰。
    这题使用哈希表保存键值对,使用双向链表维护节点的使用时间。
    双向链表之间,每次put或get操作,都将该节点放到链表头, 如果容量满了,就淘汰队尾的节点。

    1. class LRUCache {
    2. // 建一个双向链表,保存键值对
    3. class Node{
    4. Node prev;
    5. Node next;
    6. int key;
    7. int value;
    8. public Node(int key, int value) {
    9. this.key = key;
    10. this.value = value;
    11. }
    12. public Node() { }
    13. }
    14. // 哈希表保存键值对
    15. Map<Integer, Node> map;
    16. // 双向链表的头和尾
    17. Node dummyHead;
    18. Node dummyTail;
    19. // LRU的最大容量
    20. int capacity;
    21. public LRUCache(int capacity) {
    22. this.capacity = capacity;
    23. map = new HashMap<>(capacity);
    24. dummyHead = new Node();
    25. dummyTail = new Node();
    26. // 初始没有值,头直接指向尾
    27. dummyHead.next = dummyTail;
    28. dummyTail.prev = dummyHead;
    29. }
    30. public int get(int key) {
    31. // 如果key不存在,直接返回-1
    32. if (!map.containsKey(key)) {
    33. return -1;
    34. }
    35. Node node = map.get(key);
    36. // node先拿出来
    37. node.prev.next = node.next;
    38. node.next.prev = node.prev;
    39. // 再放到head后面
    40. node.next = dummyHead.next;
    41. node.prev = dummyHead;
    42. dummyHead.next.prev = node;
    43. dummyHead.next = node;
    44. return node.value;
    45. }
    46. public void put(int key, int value) {
    47. // 如果key已经存在,那就不会发生淘汰,直接将node放到链表头即可
    48. if (map.containsKey(key)){
    49. Node node = map.get(key);
    50. node.value = value;
    51. // node先拿出来
    52. node.prev.next = node.next;
    53. node.next.prev = node.prev;
    54. // 再放到head后面
    55. node.next = dummyHead.next;
    56. node.prev = dummyHead;
    57. dummyHead.next.prev = node;
    58. dummyHead.next = node;
    59. }else {
    60. // 如果容量已经满了,淘汰最后一个节点
    61. if (map.size() >= capacity) {
    62. // 删除最后一个node
    63. Node deleteNode = dummyTail.prev;
    64. map.remove(deleteNode.key);
    65. deleteNode.prev.next = deleteNode.next;
    66. deleteNode.next.prev = deleteNode.prev;
    67. }
    68. // 把新节点放到链表头
    69. Node node = new Node(key, value);
    70. node.next = dummyHead.next;
    71. node.prev = dummyHead;
    72. dummyHead.next.prev = node;
    73. dummyHead.next = node;
    74. // 放到哈希表里
    75. map.put(key, node);
    76. }
    77. }
    78. }