leetcode 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
提示:
-
1 <= capacity <= 3000 -
0 <= key <= 10000 -
0 <= value <= 105 -
最多调用
2 * 105次get和put
Related Topics
设计
哈希表
链表
双向链表
思路1:双向链表+哈希表
用队列维护放入的顺序,但是get时需要遍历链表,时间复杂度为O(n)。所以因为哈希表建立key和链表中节点位置的映射。
但是时间效果很差,经过思考,发现哈希表中维护的是链表中存储的值,而链表中查询这个这个值时需要去遍历链表,时间复杂度为O(n)而导致时间消耗过高。
class LRUCache {
private int capacity;
//哈希表用来维护 key和链表中节点的位置
HashMap<Integer,int[]> map = new HashMap<>();
//链表用来存储数据
private Queue<int[]> queue = new LinkedList<>();
public LRUCache(int capacity) {
this.capacity = capacity;
}
public int get(int key) {
if(map.containsKey(key)){
//找个这个节点
int[] nums = map.get(key);
//更新链表中的结构
queue.remove(nums);
queue.offer(nums);
return nums[1];
}
return -1;
}
//最新使用的插入到队列中 那么第一个就是最久未使用的
public void put(int key, int value) {
//队列中存在key 更新value的值
int[] nums = null;
if(map.containsKey(key)){
nums = map.get(key);
nums[1] = value;
queue.remove(nums);
queue.offer(nums);
return;
}
//不存在key
//容量满了 逐出 最久未使用的关键字。
if(queue.size() == capacity){
nums = queue.poll();
map.remove(nums[0]);
}
nums = new int[]{key,value};
queue.offer(nums);
map.put(key,nums);
}
}
解答成功:
执行耗时:977 ms,击败了5.06% 的Java用户
内存消耗:111.1 MB,击败了30.83% 的Java用户
改进:
由上述发现,使用jdk的队列(双向链表)无法达到O(1)的时间复杂度,所以可以自己实现双向链表,实现在链表中以O(1)的时间复杂度查找。
public class LRUCache {
class LinkedNode{
int key;
int value;
LinkedNode prev;
LinkedNode next;
public LinkedNode(){}
public LinkedNode(int key,int value){
this.key = key;
this.value = value;
}
}
//哈希表用来维护 key和链表中节点的位置
private HashMap<Integer,LinkedNode> map = new HashMap<>();
private LinkedNode head,tail;
private int capacity; //总容量
private int size;//链表长度
public LRUCache(int capacity) {
this.capacity = capacity;
this.size = 0;
//空的头结点
this.head = new LinkedNode();
this.tail = new LinkedNode();
this.head.next = this.tail;
this.tail.prev = this.head;
}
public int get(int key) {
LinkedNode node = map.get(key);
if(node != null){
moveToHead(node);
return node.value;
}
return -1;
}
//最新使用的插入到队列中 那么第一个就是最久未使用的
public void put(int key, int value) {
LinkedNode node = map.get(key);
if(node == null){
//不存在队列中
LinkedNode newNode = new LinkedNode(key,value);
map.put(key,newNode);
addToHead(newNode);
size++;
if(size > capacity){
map.remove(removeTail(tail).key);
}
}else{//队列中存在key 更新value的值
node.value = value;
moveToHead(node);
}
}
private void moveToHead(LinkedNode p ){
remove(p);
addToHead(p);
}
private LinkedNode removeTail(LinkedNode p){
LinkedNode node = p.prev;
remove(node);
return node;
}
//删除p
private void remove(LinkedNode p){
p.prev.next = p.next;
p.next.prev = p.prev;
p.prev = null;
p.next = null;
}
//在头部添加元素
private void addToHead(LinkedNode p){
p.next = head.next;
p.prev = head;
head.next.prev = p;
head.next = p;
}
}









