面试题 16.25. LRU 缓存
设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。
它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
示例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
方法一:
- 创建一个Map -map保存 <key,val>
- 创建一个Map-time保存 <key,time>
- get(key)时
- 如果存在,更新time(key,0),其余的key对应的time-1
- 如果不存在,返回-1
- put(key,val)时
- 未未满时
- 是否存在
- 如果存在,更新(key,val),更新time(key,0),其余的key对应的time-1
- 如果不存在, 添加map(key,val),添加time(key,0),其余的key对应的time-1;
- 是否存在
- 满时
- 是否存在
- 如果存在,更新(key,val),更新time(key,0),其余的key对应的time-1
- 如果不存在,删除time中最小的val,删除map对应的key,添加map(key,val),添加time(key,0),其余的key对应的time-1;
- 是否存在
- 未未满时
package 力扣;
import java.util.HashMap;
// 实现了但是超时
public class LRUCache {
public static void main(String[] args) {
LRUCache lruCache=new LRUCache(2);
System.out.println(lruCache.get(2));
lruCache.put(2,6);
System.out.println(lruCache.get(1));
lruCache.put(1,5);
lruCache.put(1,2);
System.out.println(lruCache.get(1));
System.out.println(lruCache.get(2));
}
HashMap<Integer,Integer> map=new HashMap<>();
HashMap<Integer,Integer> time=new HashMap<>();
int capacity;
int usedCapacity;
public LRUCache(int capacity) {
this.capacity=capacity;
usedCapacity=0;
}
public int get(int key) {
if (map.containsKey(key)) {
// 更新值
changeTime(key);
return map.get(key);
}
return -1;
}
public void changeTime(Integer key){
// 更新值
if(time.isEmpty()){
time.put(key,0);
}else {
for (Integer integer : time.keySet()) {
time.put(integer,time.get(integer)-1);
}
time.put(key,0);
}
}
public Integer getLRU(){
int min=1;
int key=0;
for (Integer integer : time.keySet()) {
if(time.get(integer)<min){
min=time.get(integer);
key=integer;
}
}
return key;
}
public void put(int key, int value) {
// 情况一 容量尚未占满
if(usedCapacity<capacity){
if(map.containsKey(key)){
map.put(key,value);
// 更新值
changeTime(key);
}else {
map.put(key,value);
usedCapacity++;
// 更新值
changeTime(key);
}
}else {
// 情况二 容量已经被占满 需要删除一个最近未使用的
if(usedCapacity==capacity){
if(map.containsKey(key)){
map.put(key,value);
// 更新值
changeTime(key);
}else{
Integer lruKey = getLRU();
map.remove(lruKey);
time.remove(lruKey);
map.put(key,value);
//更新值
changeTime(key);
}
}
}
}
}
方法二:
- 使用双向链表
- Node {int key,int val,Node pre,Node next}
package 力扣;
// 上一个方法太笨了の 这里使用最基础的双向链表实现
import javax.sound.midi.Soundbank;
import java.time.temporal.ValueRange;
public class LRUCache2 {
public static void main(String[] args) {
LRUCache2 cache = new LRUCache2( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
System.out.println(cache.get(1));
cache.put(3, 3); // 该操作会使得密钥 2 作废
System.out.println(cache.get(2));
cache.put(4, 4); // 该操作会使得密钥 1 作废
System.out.println(cache.get(1));
System.out.println(cache.get(3));
System.out.println(cache.get(4));
}
LinkList link;
Integer capacity;
Integer usedCapacity;
public LRUCache2(Integer capacity) {
this.capacity = capacity;
usedCapacity=0;
link=new LinkList();
}
public int get(int key) {
// 获取这个数的值 ,先删除它,在添加他,这样他就在首位,删除时他是不是最后一个
return link.getVal(key);
}
public void put(int key, int value) {
//容量未满
if(usedCapacity<capacity){
// 更新数值或者添加
link.NoFullADD(key,value);
usedCapacity++;
}
//容量满了
else {
// 容量满了的条件下,更新数值或者添加
link.FullADD(key,value);
}
}
}
class LinkList{
Node head;
Node end;
public LinkList() {
head=new Node();
}
public void bl(){
Node p=head.next;
System.out.print("当前链表:");
while (p!=null){
System.out.print("key:"+p.key+"-value:"+ p.val+",");
}
System.out.println();
}
public void add(int key,int val){
if(head.next==null){
Node p=new Node(key,val);
head.next=p;
p.pre=head;
end=p;
}
else {
Node p=new Node(key,val);
Node temp=head.next;
head.next=p;
p.next=temp;
p.pre=head;
temp.pre=p;
}
}
// 获取这个数,并更新这个数在链表的位置
public int getVal(int key) {
Node p=head.next;
while(p!=null){
//假如找到了
if(p.key==key){
int val=p.val;
// 如果P在末尾,不能直接删除需要更改end位置
if(p.next==null){
end=p.pre;
end.next=null;
add(key,val);
return val;
}
//P不在末尾
else {
Node pre=p.pre;
Node next = p.next;
pre.next=next;
next.pre=pre;
add(key,val);
return val;
}
}
p=p.next;
}
//找不到
return -1;
}
// 容量未满时添加 注意:需要判断是更新还是添加
public void NoFullADD(int key, int value) {
Node p=head.next;
while (p!=null){
// 找到了 为更新,首先要删除他,在添加他
if(p.key==key){
//如果为末尾节点,记得更新end位置
if(p.next==null){
end=p.pre;
end.next=null;
add(key,value);
return;
}
//如果为中间节点,
else {
Node pre = p.pre;
Node next = p.next;
pre.next=next;
next.pre=pre;
add(key,value);
return;
}
}
p=p.next;
}
//未找到,直接添加
add(key,value);
}
//此时链表已满
public void FullADD(int key, int value) {
Node p =head.next;
//如果P已经存在
while(p!=null){
if(p.key==key){
//如果为末尾节点,记得更新end位置
if(p.next==null){
end=p.pre;
end.next=null;
add(key,value);
return;
}
//如果为中间节点,
else {
Node pre = p.pre;
Node next = p.next;
pre.next=next;
next.pre=pre;
add(key,value);
return;
}
}
p=p.next;
}
// 如果P不存在,需要删除最后一个节点,在添加
end=end.pre;
end.next=null;
add(key,value);
}
}
class Node{
Node pre;
Node next;
Integer key;
Integer val;
public Node() {
}
public Node(Integer key,Integer val) {
this.key= key;
this.val = val;
}
}