思路
具体的思路在LeetCode官方解答中有,里面还有动画和视频,推荐看不懂下面思路的可以去看一下。
-
LRU 缓存机制可以通过哈希表辅以双向链表实现,我们用一个哈希表和一个双向链表维护所有在缓存中的键值对。
-
双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的。
-
哈希表即为普通的哈希映射(
HashMap
),通过缓存数据的键映射到其在双向链表中的位置。
这样以来,我们首先使用哈希表进行定位,找出缓存项在双向链表中的位置,随后将其移动到双向链表的头部,即可在O(1)O(1)
的时间内完成 get 或者 put 操作。具体的方法如下:
- 对于
get
操作,首先判断key
是否存在: - 如果
key
不存在,则返回 -1; - 如果
key
存在,则key
对应的节点是最近被使用的节点。通过哈希表定位到该节点在双向链表中的位置,并将其移动到双向链表的头部,最后返回该节点的值。 - 对于
put
操作,首先判断key
是否存在:
如果
key
不存在,使用key
和value
创建一个新的节点,在双向链表的头部添加该节点,并将key
和该节点添加进哈希表中。然后判断双向链表的节点数是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的项;
如果
key
存在,则与 get 操作类似,先通过哈希表定位,再将对应的节点的值更新为value
,并将该节点移到双向链表的头部。
- 上述各项操作中,访问哈希表的时间复杂度为
O(1)O(1)
,在双向链表的头部添加节点、在双向链表的尾部删除节点的复杂度也为O(1)O(1)
。而将一个节点移到双向链表的头部,可以分成「删除该节点」和「在双向链表的头部添加节点」两步操作,都可以在O(1)O(1)
时间内完成。
注意:在双向链表的实现中,可以使用一个伪头部(dummy head)和伪尾部(dummy tail)标记界限,这样在添加节点和删除节点的时候就不需要检查相邻的节点是否存在
不使用内置的LinkedHashMap ,使用哈希表+双向链表
代码
class LRUCache {
//定义双链表节点类
class Node{
public int key,val;
public Node next,prev;
public Node(int k ,int v){
this.key = k;
this.val = v;
}
}
//用Node类型构建一个双向链表
class DoubleList{
//定义一个头尾伪节点,这样在添加和删除节点的时候就不需要检查相邻的节点是否存在了
private Node head,tail;
//链表的元素个数
private int size;
public DoubleList(){
//初始化双链表的数据
head = new Node(0,0);
tail = new Node(0,0);
head.next = tail;
tail.prev = head;
size = 0;
}
//在链表的尾部添加节点x,时间复杂度尾0(1)
public void addLast(Node x){
x.prev = tail