LeetCode Notes_#146 LRU缓存机制
Contents
题目

解答
方法1:哈希表
感觉这个题目挺有意思,我用HashMap
实现了一个基本的解法,能通过除了一个大数据输入用例的其他所有用例,说明逻辑上是没有问题的。但是需要优化时间复杂度。
class LRUCache {
HashMap<Integer, Integer> container;
HashMap<Integer, Integer> noVisitTimes;
int cap;
int longestNoUseKey;
public LRUCache(int capacity) {
container = new HashMap<>();
noVisitTimes = new HashMap<>();
cap = capacity;
longestNoUseKey = -1;
}
public int get(int key) {
if(container.size() == 0) return -1;
for(Integer k: container.keySet()){
if(k == key) noVisitTimes.put(key, 0);
else noVisitTimes.put(k, noVisitTimes.getOrDefault(k, 0) + 1);
}
if(container.containsKey(key)) return container.get(key);
return -1;
}
public void put(int key, int value) {
for(Integer k: container.keySet()){
if(k == key) noVisitTimes.put(key, 0);
else noVisitTimes.put(k, noVisitTimes.getOrDefault(k, 0) + 1);
}
if(container.containsKey(key)){
container.put(key, value);
}else{
if(container.size() < cap){
container.put(key, value);
}else{
int maxNoUseTimes = -1;
for(Integer k: noVisitTimes.keySet()){
if(noVisitTimes.get(k) > maxNoUseTimes){
maxNoUseTimes = noVisitTimes.get(k);
longestNoUseKey = k;
}
}
container.remove(longestNoUseKey);
noVisitTimes.remove(longestNoUseKey);
container.put(key, value);
}
}
}
}
复杂度分析
时间复杂度:对于get()
和put()
的操作,每次都需要遍历一遍大小为container.size()
的hashmap
,复杂度为O(n),n为container.size()
空间复杂度:O(n)
,借助了一个额外的noVisitTimes
的hashmap,大小和container相同
方法2:哈希表+双向链表
方法1使用哈希表存储<key, value>
的键值对,这里修改为存储<key, DLinkedNode>
的键值对。DLinkedNode
是双向链表数据结构的节点。
问题的核心在于: 在缓存区满的情况下,要如何决定删除哪一个内容?当然是删除最旧的内容。
难点在于: 如何维护缓存内容的“新旧程度”,我们可以用双向链表来表达“新旧程度”,即新的内容在链表头部,旧的内容在链表尾部。
为什么一定要用双向链表? 主要是在删除内容时,需要删除链表当中的对应节点,如果用单向链表,删除一个节点时,需要给出这个节点的前序节点,比较麻烦。而双向链表的每个节点都是可以直接访问到它的前序节点的,这样删除起来会比较方便。
class LRUCache {
//定义双向链表的节点数据结构
class DLinkedNode{
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode(){}
public DLinkedNode(int _key, int _value){
key = _key;
value = _value;
}
}
//哈希表->查找缓存内容的“线头”
private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
private int size;
private int capacity;
private DLinkedNode head, tail;
//构造方法:初始化所有类变量,构造双向链表的头尾伪节点
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
//根据输入的key查找内容
public int get(int key) {
DLinkedNode node = cache.get(key);
if(node == null) return -1;
//如果key存在,先通过哈希表定位到双向链表的节点,再把节点移动到头部
moveToHead(node);
return node.value;
}
//新增输入的键值对
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if(node == null){
//如果key不存在,创建一个新的节点
DLinkedNode newNode = new DLinkedNode(key, value);
//加入哈希表
cache.put(key, newNode);
//加入到双向链表的头部
addToHead(newNode);
++size;
if(size > capacity){
//如果超出容量,删除双向链表的尾部节点
DLinkedNode tail = removeTail();
//删除哈希表当中对应的项
cache.remove(tail.key);
--size;
}
}else{//如果key存在,先通过哈希表定位,再修改value,移动到头部
node.value = value;
moveToHead(node);
}
}
//双向链表的一些方法
//addToHead:put()新内容的时候,需要把新加入的键值对插入到双向链表的头部,表示这是最新的内容
private void addToHead(DLinkedNode node){
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
//这里的removeNode()是removeTail()的工具方法
private void removeNode(DLinkedNode node){
node.prev.next = node.next;
node.next.prev = node.prev;
}
//put()时更新了一个原有的内容,这个内容变成了最新内容,需要移动到双向链表的头部;get()时查找了某个内容,这个内容也需要移动到双向链表的头部
private void moveToHead(DLinkedNode node){
removeNode(node);
addToHead(node);
}
//removeNode():put()新内容后,如果超出了cache的capacity,就需要把最后一个节点(即最旧的内容)删除,
private DLinkedNode removeTail(){
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}
复杂度分析
时间复杂度:O(1)
空间复杂度:O(capacity)