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; } }