赞
踩
请你设计并实现一个满足 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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lru-cache
(1)LinkedHashMap
JDK 中的 LinkedHashMap
本身就是将双向链表和 HashMap
进行结合的一种数据结构,我们可以直接使用它去实现 LRU 算法。不过默认的LinkedHashMap
并不会淘汰数据,所以我们需要重写它的 removeEldestEntry()
方法,当数据数量达到预设上限后,淘汰数据,构造函数中的 accessOrder
设为 true
意为按照访问的顺序排序。整个实现的代码量并不大,主要都是应用 LinkedHashMap
的特性。
(2)哈希表 & 双向链表
思路参考本题官方题解。
(1)LRU 缓存机制可以通过哈希表 & 双向链表实现,我们用一个哈希表和一个双向链表维护所有在缓存中的键值对。
(2)这样一来,我们首先使用哈希表进行定位,找出缓存项在双向链表中的位置,随后将其移动到双向链表的头部,即可在 O(1)
的时间内完成 get 或者 put 操作。具体的方法如下:
get
操作,首先判断 key 是否存在:
put
操作,首先判断 key 是否存在:
(3)上述各项操作中,访问哈希表的时间复杂度为 O(1)
,在双向链表的头部添加节点、在双向链表的尾部删除节点的复杂度也为 O(1)
。而将一个节点移到双向链表的头部,可以分成「删除该节点」和「在双向链表的头部添加节点」两步操作,都可以在 O(1)
时间内完成。
注意:在双向链表的实现中,使用一个伪头部 (dummy head) 和伪尾部 (dummy tail) 标记界限,这样在添加节点和删除节点的时候就不需要检查相邻的节点是否存在,降低了代码的复杂度。
相关题目:
LeetCode_数据结构设计_困难_460.LFU 缓存
//思路1————LinkedHashMap class LRUCache extends LinkedHashMap<Integer, Integer> { private int capacity; public LRUCache(int capacity) { /* initialCapacity – the initial capacity loadFactor – the load factor accessOrder – the ordering mode - true for access-order, false for insertion-order */ super(capacity, 0.75F, true); this.capacity = capacity; } public int get(int key) { return super.getOrDefault(key, -1); } public void put(int key, int value) { super.put(key, value); } //重写 removeEldestEntry 方法 @Override protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) { return super.size() > capacity; } } /** * Your LRUCache object will be instantiated and called as such: * LRUCache obj = new LRUCache(capacity); * int param_1 = obj.get(key); * obj.put(key,value); */
//思路2————哈希表 & 双向链表 class LRUCache { class DLinkedNode { int key; int value; DLinkedNode prev; DLinkedNode next; public DLinkedNode() {} public DLinkedNode(int key, int value) { this.key = key; this.value = value; } } // hashMap private Map<Integer, DLinkedNode> cache; //当前大小 private int size; //总容量 private int capacity; //伪头节点和伪尾节点 private DLinkedNode dummyHead; private DLinkedNode dummyTail; public LRUCache(int capacity) { cache = new HashMap<>(); this.size = 0; this.capacity = capacity; //初始化伪头部和伪尾部节点 dummyHead = new DLinkedNode(); dummyTail = new DLinkedNode(); dummyHead.next = dummyTail; dummyTail.prev = dummyHead; } public int get(int key) { DLinkedNode node = cache.get(key); if (node == null) { return -1; } //如果 key 存在,则将 node 移到头部,这样就可以表示节点 node 最近被访问过 moveToHead(node); return node.value; } public void put(int key, int value) { DLinkedNode node = cache.get(key); if (node == null) { if (size >= capacity) { //如果超出容量,则删除双向链表的尾部节点 DLinkedNode tail = removeTail(); //删除哈希表中对应的节点 cache.remove(tail.key); size--; } //如果 key 不存在,创建一个新的节点 DLinkedNode newNode = new DLinkedNode(key, value); //将 newNode 添加到哈希表中 cache.put(key, newNode); //将 newNode 添加到双向链表的头部 addToHead(newNode); size++; } else { // key 存在,先修改 value,再将节点移动到头部 node.value = value; moveToHead(node); } } //在头部插入节点 node private void addToHead(DLinkedNode node) { node.prev = dummyHead; node.next = dummyHead.next; dummyHead.next.prev = node; dummyHead.next = node; } //移除节点 node private void removeNode(DLinkedNode node) { node.prev.next = node.next; node.next.prev = node.prev; } //将节点 node 移动到头部 private void moveToHead(DLinkedNode node) { removeNode(node); addToHead(node); } //删除尾部节点并返回 private DLinkedNode removeTail() { DLinkedNode res = dummyTail.prev; removeNode(res); return res; } } /** * Your LRUCache object will be instantiated and called as such: * LRUCache obj = new LRUCache(capacity); * int param_1 = obj.get(key); * obj.put(key,value); */
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。