LRU简介
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。
说通俗点就是假定有一个序列,然后给定一些参数:
-
capacity(容量)
-
size(当前数量)
-
Cache
每当缓存中的数据被访问时,就把这个数据放到序列首部,其他数据位置不变,当新增数据时候也会放到首部。当size大于capacity时便会移除尾部数据。
实现思路
为了实现LRU算法最好使用下面这两种数据结构:
-
哈希表
-
双向链表
首先哈希表用于存储键和数据的映射,且哈希表查找速度快,时间复杂度仅为O(1),而双向链表则是便于进行插入删除,也可以在时间复杂度为O(1)内完成。
注意这里使用了虚拟头尾结点
暂时无法在飞书文档外展示此内容
手撕!
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 <= 10(5)
-
最多调用
2 * 10(5)
次get
和put
import java.util.HashMap;
import java.util.Map;
class LRUCache {
//定义一个双向链表
class DLinkedNode {
int key;
int val;
DLinkedNode prev;
DLinkedNode next;
DLinkedNode () {}
DLinkedNode (int _key, int _val) {
this.key = _key;
this.val = _val;
}
}
//定义size和capacity
private int size;
private int capacity;
//定义哈希表形式,存储数据
private Map<Integer,DLinkedNode> cache = new HashMap<>();
//定义虚拟首尾结点
private DLinkedNode head;
private DLinkedNode tail;
//添加首结点
private void addHeadNode(DLinkedNode node) {
node.next = head.next;
head.next.prev = node;
node.prev = head;
head.next = node;
}
//移除结点
private void deleteNode(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
//将结点移动到首部
private void moveNodeToHead(DLinkedNode node) {
//删除这个结点,然后添加这个结点到头部
addHeadNode(node);
deleteNode(node);
}
//移除尾结点
private int deleteTailNode() {
int tailKey = tail.prev.val;
deleteNode(tail.prev);
return tailKey;
}
public LRUCache(int capacity) {
//初始化
this.size = 0;
this.capacity = capacity;
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
return node.val;
}
public void put(int key, int value) {
//如果关键字 key 已经存在,则变更其数据值 value
DLinkedNode node = cache.get(key);
if (node != null && node.val != value) {
node.val = value;
} else if (node != null && node.val == value) {
moveNodeToHead(node);
}
else if (node == null) {
DLinkedNode newNode = new DLinkedNode(key,value);
cache.put(key,newNode);
addHeadNode(newNode);
size++;
}
//如果size > capacity,删除尾结点
if (size > capacity) {
int tailKey = deleteTailNode();
cache.remove(tailKey);
size--;
System.out.println(tailKey);
}
}
}
/**
* 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);
*/
参考文章: