解法都在代码里,不懂就留言或者私信,这个题大概率不会让你直接写代码,而是说以下思路,如果写代码这个题写出来基本就过了
class LRUCache {
/**首先我们得有缓存,get和put都是O(1)时间复杂度,我们常用的数据结构里应该只有Hashmap */
Map<Integer, DoubleLinkedListNode> cache;
int capacity;
/**我们自定义了一个双向链表用于淘汰的操作*/
DoubleLinkedList dll;
public LRUCache(int capacity) {
this.capacity = capacity;
this.cache = new HashMap<>();
this.dll = new DoubleLinkedList();
}
public int get(int key) {
/**我们想想cache这个map为什么是Map<Integer, DoubleLinkedListNode>而不是Map<Integer, Integer> ,因为我们要操作链表*/
if(!cache.containsKey(key)) {
return -1;
}
DoubleLinkedListNode node = cache.get(key);
dll.moveToTail(node);
return node.val;
}
public void put(int key, int value) {
/**往里放值的时候需要判断这个值在原来的缓存中存在不存在,如果存在只是一个缓存的更新操作,更新完之后把这个节点从链表中某个位置移动到尾部即可 */
if(cache.containsKey(key)) {
DoubleLinkedListNode node = cache.get(key);
node.val = value;
dll.moveToTail(node);
} else {
/**如果不存在的话需要判断缓存是不是已经满了,如果满了就涉及淘汰了,淘汰哪个?淘汰双向链表头部的,因为它是访问时间距离现在最远的*/
if(cache.size() == capacity) {
DoubleLinkedListNode head = dll.removeHead();
cache.remove(head.key);
}
/**包装成节点 */
DoubleLinkedListNode node = new DoubleLinkedListNode(key, value);
/**放入map */
cache.put(key, node);
/**放入双向链表 */
dll.addNode(node);
}
}
/**定义双向链表节点和双向链表类*/
static class DoubleLinkedListNode {
/**属性有前驱和后继节点以及自己的值 */
DoubleLinkedListNode pre;
DoubleLinkedListNode next;
int key;
int val;
public DoubleLinkedListNode(int key, int val) {
this.key = key;
this.val = val;
}
}
static class DoubleLinkedList {
/**对于双向链表来说,其实和单向链表没啥不一样的,都是有头有尾
中间是双向还是单向靠节点的属性实现就行了,这里我也不写set get了,我们是写算法不是真实的项目,没必要的*/
DoubleLinkedListNode head;
DoubleLinkedListNode tail;
/**加入节点是最基础的操作,这里就是加入节点,至于满不满,放在LRUCache里判断*/
public void addNode(DoubleLinkedListNode node) {
if(head == null) {
/**还没有加入过节点,那加入了之后它既是头又是尾 */
head = tail = node;
} else {
/**当前节点放在最后作为尾巴 */
tail.next = node;
/**千万不要忘了pre指针 */
node.pre = tail;
tail = node;
}
}
/**LRU缓存的特点是哪个值的访问时间离现在越近,就是最热的应该保存,哪个离现在最远,应该就是缓存满了的时候被淘汰的
我们这里双向链表用在LRU缓存里,需要有几个方法1. moveToTail用于某个key被访问了之后把它挪到尾部
2.removeHead用于缓存满了之后淘汰(head放的是访问时间离现在最远的)
removeHead需要有返回值,因为hashMap要同步删除*/
public DoubleLinkedListNode removeHead() {
/**强壮性校验,其实用不到,习惯了*/
if(head == null) {
return null;
}
/**拿到老的头部用于返回 */
DoubleLinkedListNode oldHead = head;
/**它的下一个节点作为新的头 */
DoubleLinkedListNode next = head.next;
/**断开和下一个节点的连接 */
head.next = null;
/**指认下个节点位头 */
head = next;
/**只有一个节点的时候next是不存在的,所以这里要判空 */
if(head != null) {
head.pre = null;
}
return oldHead;
}
/**某个数据被访问了之后变成热点数据的操作 */
public void moveToTail(DoubleLinkedListNode node) {
if(node == tail) {
/**本来就在尾部,就别移动了把 */
return;
}
/**如果是头部的话则涉及换头的操作*/
if(node == head) {
DoubleLinkedListNode next = node.next;
/**不管if还是else里都有断开和后面节点连接的操作,放在最后的统一处理里 */
head = next;
head.pre = null;
} else {
/**不在尾部必有前驱,不在头部必有后继,把前驱和后继连一起*/
DoubleLinkedListNode pre = node.pre;
DoubleLinkedListNode next = node.next;
pre.next = next;
next.pre = pre;
}
/**最后进行通用的放在尾部的操作,放在尾部就把next置为null,两个原因:1尾部本来就没有后继 2.与原来的next断开连接 */
node.next = null;
tail.next = node;
/**一共要注意pre指针 */
node.pre = tail;
tail = node;
}
}
}
/**
* 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);
*/
这个题我的目的就是写出来,不知道是不是最优解,欢迎指导