方法一:哈希+双链表
LRU缓存就是在容量满了之后,又有新的元素需要存入时,这时会优先丢弃掉最近最久未使用的元素。
使用双向链表维护一个容量,当元素被读取(或其他操作)时,就把这个元素放到链表头,更新它的使用时间。
HashMap的作用确定读取的节点在双向链表中输入哪一个节点。
class LRUCache {
class Node {//结点类,存放key-value对象元素,有前域和后域
public int key, val;
public Node next, prev;
public Node(int k, int v) {
this.key = k;
this.val = v;
}
}
class DoubleList {//双向链表类:由Node类连成,有将节点添加到头部,删除指定节点,删除尾节点,和容量四个方法
//DoubList的四个方法的实现,是重中之重!
private Node head, tail;//定义双链表头,表尾结点
private int size;
public void addFirst(Node node) {//将node结点添加到链表首部
if (head == null) {//如果头部为空,即整个链表都没有结点
head = tail = node;
} else {//
Node n = head;
//head.prev=node,node.next=head,把node放在了head的前面
n.prev = node;
node.next = n;
//node成为链表头
head = node;
}
size++;//加入后容量+1,//这个方法的实现只做了添加,不能当做移动理解
}
//去除方法
public void remove(Node node) {
if (head == node && tail == node) {//如果只有当前一个结点,就直接删除就好
head = null;
tail = null;
} else if (tail == node) {//如果当前结点在尾部,则断开当前结点和前一个结点的指针,并把尾指针前移
node.prev.next = null;
tail = node.prev;
} else if (head == node) {//如果当前结点在首部,则断开当前结点和后一个结点的指针,并把头指针后移一个结点
node.next.prev = null;
head = node.next;
} else {//如果当前结点在双向链表中间,则断开前后的指针就可以了,即跳过当前结点。
node.prev.next = node.next;
node.next.prev = node.prev;
}
size--;//容器-1
}
public Node removeLast() {//删除一个结点,调用了Remove()方法
Node node = tail;//先用node记录下最后一个结点
remove(tail);//再删除
return node;//再返回这个已经删除掉的结点,让map也删除掉
}
public int size() {
return size;
}
}
//准备工作完毕,开始写具体算法
private HashMap<Integer, Node> map;//HashMap,用来将存放节点
private DoubleList cache;
private int cap;
public LRUCache(int capacity) {//初始化LRUCache,LRU缓存由一个哈希MAP和一个双链表组成。哈希用来存放双链表中的节点,方便双链表去找到对应的节点。
this.cap = capacity;
map = new HashMap<>();
cache = new DoubleList();
}
//LRU缓存的读取方法
public int get(int key) {//LRU缓存读方法,需要先判断map中是否存在该节点,如过存在,就返回该节点的值,并将该节点移到双向链表头部,否则,返回-1。
if(!map.containsKey(key)) return -1;//使用map的containsKey()方法。
int val = map.get(key).val;//map.get(key)方法,得到该目标节点,该得到目标节点的值。
put(key, val);//LRU缓存的put方法,更新该节点的位置为双向链表的头部。
return val;
}
//LRU缓存的存入或更新方法
public void put(int key, int value) {
Node x = new Node(key, value);
if (map.containsKey(key)){//如果map存在了这个节点,就将该节点删除,并且吧这个节点添加到链表首部,相当于把这个节点移动到了首
cache.remove(map.get(key));
cache.addFirst(x);//addFirst方法,把该结点添加到首部
map.put(key,x);//重新把这个节点放入map中。
} else {//如果map中没有该节点,容器没有满,就把他直接加入到首部
if (cap == cache.size()) {//如果容量刚好满了,就把尾节点(最近最久未使用的节点)删除掉,把该节点加入首部。
Node last = cache.removeLast();//因为map中也需要删除掉改丢弃的结点,所以需要通过removeLast()获得该结点
map.remove(last.key);//把map中的丢弃的及诶单也删除掉。
}
cache.addFirst(x);
map.put(key,x);
}
}
}
/**
* 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);
*/
方法二:继承LinkedHashMap,实现removeEldestEntry()方法。但是面试时时不推荐
class LRUCache extends LinkedHashMap<Integer, Integer>{
private int capacity;
public LRUCache(int capacity) {
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);
}
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > capacity;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/lru-cache/solution/lruhuan-cun-ji-zhi-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
···java