一、题目
题目:146. LRU 缓存机制
难度:中等
地址:https://leetcode-cn.com/problems/lru-cache/
使用语言:Java
二、代码
1. 利用Java 集合API
class LRUCache {
/*
在这个过程中,要让put和get方法的实践复杂度为(i) cache这个数据结构必备的条件如下:
1.显然`cache`中的元素必须有时序,以区分最近使用和久未使用的数据,当容量满了之后要删除久未使用的元素,为后面新添加的元素腾出来空间
2.快速的在`cache`中找到key对应的value
3.`cache`要持之在任意位置快速插入和删除元素
*/
int cap;
LinkedHashMap<Integer,Integer> cache = new LinkedHashMap<>();
public LRUCache(int capacity) {
this.cap = capacity;
}
public int get(int key) {
if(!cache.containsKey(key)){
return -1;
}
// 将key变为最近使用
makeRecently(key);
return cache.get(key);
}
public void put(int key, int value) {
if(cache.containsKey(key)){
// 修改key的值
cache.put(key,value);
// 将key变为最近使用
makeRecently(key);
return;
}
if(cache.size() >= this.cap){
// 链表头部就是最久未使用的key
int oldestKey = cache.keySet().iterator().next();
cache.remove(oldestKey);
}
// 将新的key 添加到链表尾部
cache.put(key,value);
}
private void makeRecently(int key){
int val = cache.get(key);
// 删除key 重新插入到对尾
cache.remove(key);
cache.put(key,val);
}
}
/**
* 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.实现LRU算法
class LRUCache {
// key -> Node(key,val)
private HashMap<Integer,Node> map;
// Node(k1,v1) <-> Node(k2,v2)
private DoubleList cache;
// 最大容量
private int cap;
public LRUCache(int capacity) {
this.cap = capacity;
map = new HashMap<>();
cache = new DoubleList();
}
public int get(int key) {
if(!map.containsKey(key)){
return -1;
}
// 将数据提升为最近使用的
makeRecently(key);
return map.get(key).val;
}
public void put(int key, int value) {
if(map.containsKey(key)){
// 删除旧的数据
deleteKey(key);
// 新插入的数据为最近使用的数据
addRecently(key,value);
return;
}
if(cap == cache.size()){
// 删除最久未使用的元素
removeLeastRecently();
}
// 添加为最近未使用的元素
addRecently(key,value);
}
//尽量让LRU的主方法get和put避免直接操作map和cache的细节
private void makeRecently(int key){
Node x = map.get(key);
// 先从链表中删除这个节点
cache.remove(x);
// 重新插到对位
cache.addLast(x);
}
// 添加最近使用的元素
private void addRecently(int key,int val){
Node x = new Node(key,val);
// 链表尾部就是最近使用的元素
cache.addLast(x);
// 在map中添加key的映射
map.put(key,x);
}
// 删除某一个key
private void deleteKey(int key){
Node x = map.get(key);
// 从链表中删除
cache.remove(x);
// 从map中删除
map.remove(key);
}
// 删除久未使用的元素
private void removeLeastRecently(){
// 链表头部的第一个元素就是久未使用的
Node deletedNode = cache.removeFirst();
// 从map中删除对应的key
int deletedKey = deletedNode.key;
map.remove(deletedKey);
}
// 实现一个双链表
// 此处实现的双链表只能从尾部插入 也就是说尾部的数据是最近使用的 靠头部的数据是最久未使用的
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 时间复杂度为O(1)
public void addLast(Node x){
x.prev = tail.prev;
x.next = tail;
tail.prev.next = x;
tail.prev = x;
size++;
}
// 删除链表中的x节点(x 一定存在)
// 由于是双链表且给的是目标Node节点 时间复杂度为O(1)
public void remove(Node x){
x.prev.next = x.next;
x.next.prev = x.prev;
size--;
}
// 删除链表中第一个节点 并返回该节点 时间复杂度为O(1)
public Node removeFirst(){
if(head.next == tail){
return null;
}
Node first = head.next;
remove(first);
return first;
}
// 返回链表长度,时间复杂度为O(1)
public int size(){
return size;
}
}
// 双链表节点类
class Node{
public int key,val;
public Node next,prev;
public Node(int k,int v){
this.key = k;
this.val = v;
}
}
}
/**
* 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);
*/