本题其实就是需要实现LRU算法,并且要求复杂度是O(1),所以我们就需要结合数据结构来思考,我们可以采用双向链表+哈希表的结构,通过hashmap的key添加元素key,value添加双向链表的节点,其中节点用来存储题中给的值。这样就能满足复杂度要求了。
关于LRU算法,可以参考灵茶山艾府的讲解,非常通俗易懂。
这里需要注意,如果我们要添加元素,首先需要判断容量是否够,如果不够,则需要删除最底下的书,如果够则需要判断是否已经存在这本书,如果不存在直接添加,存在则需要把之前的书给覆盖掉。
如果我们要查询书,直接查询hashmap,如果里面有则返回这本书,并且把这本书放到最上面,如果没有返回-1即可。
题目中的dummy节点其实就是双向链表的虚拟头节点,可以说通过它把双向链表变成了双向循环链表!这样就方便我们的操作了。
public class LRUCache {
private static class Node {//定义链表类
int key, value;//因为我们在删除最后一次使用的数据的时候,需要知道key才可以。
Node prev, next;
Node(int k, int v) {
key = k;
value = v;
}
}
private final int capacity;
private final Node dummy = new Node(0, 0); // 哨兵节点(虚拟节点)
private final Map<Integer, Node> keyToNode = new HashMap<>();
public LRUCache(int capacity) {
this.capacity = capacity;
dummy.prev = dummy;//虚拟节点初始化,一开始其前后指针都指向自己
dummy.next = dummy;
}
public int get(int key) {
Node node = getNode(key);
return node != null ? node.value : -1;
}
public void put(int key, int value) {
Node node = getNode(key);
if (node != null) { // 有这本书
node.value = value; // 更新 value
return;
}
node = new Node(key, value); // 新书
keyToNode.put(key, node);
pushFront(node); // 放在最上面
if (keyToNode.size() > capacity) { // 书太多了
Node backNode = dummy.prev;
keyToNode.remove(backNode.key);
remove(backNode); // 去掉最后一本书
}
}
private Node getNode(int key) {
if (!keyToNode.containsKey(key)) { // 没有这本书
return null;
}
Node node = keyToNode.get(key); // 有这本书
remove(node); // 把这本书抽出来
pushFront(node); // 放在最上面
return node;
}
// 删除一个节点(抽出一本书)
private void remove(Node x) {
x.prev.next = x.next;//当前节点的后节点指向当前节点的前指针的后指针
x.next.prev = x.prev;
}
// 在链表头添加一个节点(把一本书放在最上面)
private void pushFront(Node x) {
x.prev = dummy;
x.next = dummy.next;
x.prev.next = x;//不可以把x.prev换成dummy,因为dummy是虚拟节点,x.prev应该是真实存在的节点
x.next.prev = x;
}
}