|
| 1 | +## LRU缓存 |
| 2 | +### 题目描述 |
| 3 | + |
| 4 | +运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。 |
| 5 | +获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。 |
| 6 | +写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。 |
| 7 | + |
| 8 | +示例: |
| 9 | +``` |
| 10 | +LRUCache cache = new LRUCache( 2 /* 缓存容量 */ ); |
| 11 | +
|
| 12 | +cache.put(1, 1); |
| 13 | +cache.put(2, 2); |
| 14 | +cache.get(1); // 返回 1 |
| 15 | +cache.put(3, 3); // 该操作会使得密钥 2 作废 |
| 16 | +cache.get(2); // 返回 -1 (未找到) |
| 17 | +cache.put(4, 4); // 该操作会使得密钥 1 作废 |
| 18 | +cache.get(1); // 返回 -1 (未找到) |
| 19 | +cache.get(3); // 返回 3 |
| 20 | +cache.get(4); // 返回 4 |
| 21 | +``` |
| 22 | + |
| 23 | +### 解法 |
| 24 | +用一个哈希表和一个双向链表来实现 |
| 25 | +- 哈希表保存每个节点的地址 |
| 26 | +- 链表头表示上次访问距离现在时间最短,尾部表示最近访问最少 |
| 27 | +- 访问节点,若节点存在,要把该节点放到链表头 |
| 28 | +- 插入时要考虑是否超过容量 |
| 29 | + |
| 30 | +```java |
| 31 | +//双向链表的节点 |
| 32 | +class Node{ |
| 33 | + public int key; |
| 34 | + public int val; |
| 35 | + public Node pre;//指向前面的指针 |
| 36 | + public Node next;//指向后面的指针 |
| 37 | + public Node(int key,int value){ |
| 38 | + this.val = value; |
| 39 | + this.key = key; |
| 40 | + } |
| 41 | +} |
| 42 | +class LRUCache { |
| 43 | + int capacity;//容量 |
| 44 | + Node head;//双向链表的头,维护这个指针,因为set,get时需要在头部操作 |
| 45 | + Node end;//双向链表的尾,set时,要是满了,需要将链表的最后一个节点remove |
| 46 | + HashMap<Integer,Node> map = new HashMap<Integer,Node>();//hash表 |
| 47 | + public LRUCache(int capacity) { |
| 48 | + this.capacity = capacity; |
| 49 | + } |
| 50 | + //添加,删除尾部,插入头部的操作 |
| 51 | + public void remove(Node node){ |
| 52 | + Node cur = node; |
| 53 | + Node pre = node.pre; |
| 54 | + Node post = node.next; |
| 55 | + if(pre == null){//说明cur是头部节点 |
| 56 | + head = post; |
| 57 | + } |
| 58 | + else pre.next = post;//更新指针,删除 |
| 59 | + if(post == null){//说明cur是最后的节点 |
| 60 | + end = pre; |
| 61 | + } |
| 62 | + else post.pre = pre; |
| 63 | + } |
| 64 | + public void setHead(Node node){ |
| 65 | + //直接插入 |
| 66 | + node.next = head; |
| 67 | + node.pre = null; |
| 68 | + if(head != null) head.pre = node;//防止第一次插入时为空 |
| 69 | + head = node; |
| 70 | + if(end==null) end = node; |
| 71 | + } |
| 72 | + public int get(int key) { |
| 73 | + if(map.containsKey(key)){ |
| 74 | + //需要把对应的节点调整到头部 |
| 75 | + Node latest = map.get(key); |
| 76 | + remove(latest); |
| 77 | + setHead(latest); |
| 78 | + //返回value |
| 79 | + return latest.val; |
| 80 | + } |
| 81 | + else return -1; |
| 82 | + } |
| 83 | + |
| 84 | + public void put(int key, int value) { |
| 85 | + if(map.containsKey(key)){//这个key原来存在 |
| 86 | + //只需要把key对应的node提到最前面,更新value |
| 87 | + Node oldNode = map.get(key); |
| 88 | + oldNode.val = value; |
| 89 | + remove(oldNode); |
| 90 | + setHead(oldNode); |
| 91 | + } |
| 92 | + else{ |
| 93 | + //这个key原来不存在,需要重新new出来 |
| 94 | + Node newNode = new Node(key,value); |
| 95 | + //接下来要考虑容量 |
| 96 | + if(map.size() < capacity){ |
| 97 | + setHead(newNode); |
| 98 | + map.put(key, newNode); |
| 99 | + } |
| 100 | + else{ |
| 101 | + //容量不够,需要先将map中,最不常使用的那个删除了删除 |
| 102 | + map.remove(end.key); |
| 103 | + //接下来更新双向链表 |
| 104 | + remove(end); |
| 105 | + setHead(newNode); |
| 106 | + //放入新的 |
| 107 | + map.put(key, newNode); |
| 108 | + } |
| 109 | + } |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +/** |
| 114 | + * Your LRUCache object will be instantiated and called as such: |
| 115 | + * LRUCache obj = new LRUCache(capacity); |
| 116 | + * int param_1 = obj.get(key); |
| 117 | + * obj.put(key,value); |
| 118 | + */ |
| 119 | +``` |
0 commit comments