LRU缓存
LRU缓存题目:146.LRU缓存
【题目】
请你设计并实现一个满足 [LRU (最近最少使用) 缓存]约束的数据结构。
实现 `LRUCache` 类:
- `LRUCache(int capacity)` 以 **正整数** 作为容量 `capacity` 初始化 LRU 缓存
- `int get(int key)` 如果关键字 `key` 存在于缓存中,则返回关键字的值,否则返回 `-1` 。
- `void put(int key, int value)` 如果关键字 `key` 已经存在,则变更其数值 `value` ;
如果不存在,则向缓存中插入该组 `key-value` 。
如果插入操作导致关键字数量超过 `capacity` ,则应该逐出最久未使用的关键字。
函数 `get` 和 `put` 必须以 `O(1)` 的平均时间复杂度运行。
【解答】
class LRUCache {
class DoubleNode{
DoubleNode pre;
DoubleNode next;
Integer key;
Integer val;
public DoubleNode(){}
public DoubleNode(Integer key,Integer val){
this.key = key;
this.val = val;
}
}
// key:对应的入参key
Map<Integer,DoubleNode> map = new HashMap<>();
//当前总数
int count=0;
//最大值
int max =0;
//默认头尾节点,并不是具体的键值对,起到控制首尾的作用
DoubleNode head;
DoubleNode end;
public LRUCache(int capacity) {
this.max = capacity;
head = new DoubleNode(-1,-1);
end = new DoubleNode(-1,-1);
head.next = end;
end.pre =head;
}
//将当前节点移动到首位
public void moveToHead(DoubleNode node){
node.pre = head;
node.next=head.next;
head.next.pre = node;
head.next=node;
}
//连接当前节点的前后节点(相当于移出当前节点)
public void linkPreAndNext(DoubleNode node){
node.pre.next = node.next;
node.next.pre = node.pre;
}
public int get(int key) {
if(map.get(key)==null){
return -1;
}else{
DoubleNode node = map.get(key);
//先将当前节点的前后节点连接
linkPreAndNext(node);
//然后将当前节点移动到头部
moveToHead(node);
return node.val;
}
}
public void put(int key, int value) {
if(map.get(key)==null){
DoubleNode node = new DoubleNode(key,value);
//直接添加,无需删除
if(count<max){
map.put(key,node);
moveToHead(node);
count++;
}else{//添加,且删除1个尾部
//删除最后一个元素
map.remove(end.pre.key);
linkPreAndNext(end.pre);
//插入新值
map.put(key,node);
moveToHead(node);
//这里已经删除了1个了,不需要再把count++
// count++;
}
}else{
DoubleNode node = map.get(key);
linkPreAndNext(node);
moveToHead(node);
node.val=value;
map.put(key,node);
}
}
}
LFU缓存(leetcode:460)
LFU缓存题目:146.LFU缓存
【题目】
请你为 [最不经常使用(LFU)]缓存算法设计并实现数据结构。
实现 `LFUCache` 类:
- `LFUCache(int capacity)` - 用数据结构的容量 `capacity` 初始化对象
- `int get(int key)` - 如果键 `key` 存在于缓存中,则获取键的值,否则返回 `-1` 。
- `void put(int key, int value)` - 如果键 `key` 已存在,则变更其值;
如果键不存在,请插入键值对。
当缓存达到其容量 `capacity` 时,则应该在插入新项之前,移除最不经常使用的项。
在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除最久未使用的键。
为了确定最不常使用的键,可以为缓存中的每个键维护一个 **使用计数器** 。
使用计数最小的键是最久未使用的键。
当一个键首次插入到缓存中时,它的使用计数器被设置为 `1` (由于 put 操作)。
对缓存中的键执行 `get` 或 `put` 操作,使用计数器的值将会递增。
函数 `get` 和 `put` 必须以 `O(1)` 的平均时间复杂度运行。
【解答】
class LFUCache {
int capacity;
int globalTime;
Map<Integer,Node> map;
TreeSet<Node> set;
public LFUCache(int capacity) {
this.capacity=capacity;
globalTime=0;
map = new HashMap<>();
set = new TreeSet<>();
}
public int get(int key) {
if(map.get(key)==null){
return -1;
}
//移除set中老缓存,
//构建新缓存:时间戳给到当前node,然后次数+1
//新缓存存入set和覆盖map中的缓存
Node cur = map.get(key);
set.remove(cur);
cur.cnt++;
cur.time=globalTime;
globalTime++;
set.add(cur);
map.put(key,cur);
return cur.value;
}
public void put(int key, int value) {
//如果值已存在,肯定不会超出,直接覆盖即可
if(map.containsKey(key)){
Node cur = map.get(key);
set.remove(cur);
cur.value=value;
cur.cnt++;
cur.time = globalTime;
globalTime++;
map.put(key,cur);
set.add(cur);
}else{
//判断是否要溢出,需要提前移除数据
if(set.size()>=capacity){
Node deleteNode = set.first();
set.remove(deleteNode);
map.remove(deleteNode.key);
}
Node cur = new Node(1,globalTime,key,value);
globalTime++;
set.add(cur);
map.put(key,cur);
}
}
class Node implements Comparable<Node>{
int cnt,time,key,value;
public Node(int cnt,int time,int key,int value){
this.cnt=cnt;
this.time = time;
this.key = key;
this.value =value;
}
//次数越小排在越前面,次数相同时间最早排在最前
public int compareTo(Node other){
if(cnt==other.cnt){
return time - other.time;
}else {
return cnt-other.cnt;
}
}
public boolean equals(Object other){
if(this == other){
return true;
}
if(other instanceof Node){
Node ot = (Node)other;
return this.cnt==ot.cnt
&& this.time ==ot.time;
}
return false;
}
public int hashCode(){
return cnt*1000000007 + time;
}
}
}