@(labuladong的算法小抄)[哈希链表]
leetcode 146. LRU 缓存机制
题目描述
解题思路
参考:labuladong的算法小抄P218
Java内置类型LinkedHashMap
面试官一般会期望读者能够自己实现一个简单的双向链表,而不是使用语言自带的、封装好的数据结构。在 Java 语言中,有数据结构 LinkedHashMap。这种做法不会符合面试官的要求,因此下面只给出使用封装好的数据结构实现的代码,而不多做阐述。
class LRUCache {
//cache的容量
private int cap;
//LinkedHashMap的put是插入到尾部,因此,cache的尾部代表最近使用,头部代表最久未使用
private 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 (this.cap <= 0) return;
//如果cache中已有key,则直接修改,并移到最近使用的队尾
if (cache.containsKey(key)) {
cache.put(key, value);
makeRecently(key);
return;
}
//如果当前cache容量已满,则要先删除链表头部的最久未使用的key
if (cache.size() >= this.cap) {
int oldestKey = cache.keySet().iterator().next();
cache.remove(oldestKey);
}
//将key插入到尾部
cache.put(key, value);
}
//将key变为最近使用
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);
*/
双向链表+哈希表
用自顶向下的思考方法
首先,列出get
和put
函数的需求:
然后,根据get
和put
函数的需求,明确出四个函数,将链表cache
和哈希表map
的操作封装起来,尽量让LRU的主方法get
和put
避免直接操作cache
和map
的细节。由于map有自带的函数可以移除和添加key,所以只需要接下来只需关注链表的相关操作:
最后,根据上面四个函数需要对链表进行的操作,在双向链表的类中定义出三个API函数以供调用:
class LRUCache {
//定义双链表的节点类型
class Node {
public int key, val;
public Node next, prev;
public Node(int k, int v) {
this.key = k;
this.val = v;
}
}
//依靠Node类型构建双链表,并实现LRU算法必需的几个API
class DoubleList {
//头尾虚节点
private Node head, tail;
//链表元素数
private int size;
//初始化双向链表的数据
public DoubleList() {
head = new Node(0, 0);
tail = new Node(0, 0);
head.next = tail;
head.prev = null;
tail.prev = head;
tail.next = null;
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一定存在),时间复杂度为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;
}
}
//hashmap和doublelist组合实现java内置的LinkedHashMap功能
private HashMap<Integer, Node> map;
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 (this.cap <= 0) return;
if (map.containsKey(key)) {
//删除旧的数据
removeKey(key);
//新插入的数据为最近使用的数据
addRecently(key, value);
return;
}
//如果容量不够了,就要先删除最久未使用的元素
if (cache.size() >= cap) {
removeLeastRecently();
}
addRecently(key, value);
}
/* 由于map和cache是组合实现的,因此用以下四个函数进行封装,将map和cache的操作绑定起来 */
//将某个key提升为最近使用的
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 removeKey(int key) {
Node x = map.get(key);
//从链表中删除
cache.remove(x);
//从map中删除
map.remove(key);
}
//删除最久未使用的元素
private void removeLeastRecently() {
//链表头部的第一个元素就是最久未使用的
Node removedNode = cache.removeFirst();
//别忘了从map中删除它的key
map.remove(removedNode.key);
}
}
/**
* 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);
*/