题目:设计LRU缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能
- set(key, value):将记录(key, value)插入该结构
- get(key):返回key对应的value值
[要求]
- set和get方法的时间复杂度为O(1)
- 某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的。
- 当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。
若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1
对于每个操作2,输出一个答案
方法一:使用了LinkedHashMap
我们知道了要将最近使用的节点作为最长使用的,当缓存超过k的时候,移除最不常用的。这就是按照访问顺序来访问链表,这个就让我们想到了LinkedHashMap。为了有顺序的访问LinkedHashMap在底层为了双向链表,保证了元素迭代的顺序。该顺序可以是插入顺序和访问顺序。底层数据结构是这个样子的:使用一个双向链表了记录访问顺序的。
public class Solution {
public static void main(String[] args) {
int[][] operators = {{1, 1, 1}, {1, 2, 2}, {1, 3, 2}, {2, 1}, {1, 4, 4}, {2, 2}};
Solution solution = new Solution();
int[] ints = solution.LRU(operators, 3);
System.out.println(Arrays.toString(ints));
}
public int[] LRU (int[][] operators, int k) {
int len = (int) Arrays.stream(operators).filter(operator -> operator[0] == 2).count();
int[] ret=new int[len];
LRUCache lru=new LRUCache(k);
int index=0;
for(int[] operator:operators)
{
if(operator[0]==1)
{
lru.save(operator[1],operator[2]);
}else{
if(lru.contains(operator[1])) {
ret[index]= lru.getOne(operator[1]);
index++;
}
else{
ret[index]=-1;
index++;
}
}
}
return ret;
}
final class LRUCache extends LinkedHashMap<Integer, Integer> {
//节点个数
private int limit;
//构造函数
public LRUCache(int limit)
{
super(limit,0.75f,true);
this.limit=limit;
}
//添加节点:实际内部就是LinkedHashMap的put
public void save(Integer key,Integer val)
{
put(key,val);
}
//获得节点:实际内部就是LinkedHashMap的get
public Integer getOne(Integer key)
{
return get(key);
}
public boolean contains(Integer key)
{
return containsKey(key);
}
//这个方法是必须要重写的,为了判断存在的键值对的个数是否大于限制
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size()>limit;
}
}
方法二:自己写底层
在LinkedHashMap源码解析中,知道了底层数据结构是桶数组+双向链表和红黑树。在LRU中可以不使用红黑树,只设置这个双向链表和桶数组以及其中的链表。
在set的时候,要记得按照访问顺序设置最常使用的节点(就是将这个节点放在双向链表的尾部),这样双向链表的头结点就是不常使用的节点,在链表节点个数大于K的时候,就可以删除这个节点。
在get的时候,也是要按照访问顺序设置最常使用的节点。
import java.util.*;
public class Solution {
/**
* lru design
* @param operators int整型二维数组 the ops
* @param k int整型 the k
* @return int整型一维数组
*/
public int[] LRU (int[][] operators, int k) {
int resultLength=0;
for(int[] operator:operators)
{
if(operator[0]==2){
resultLength++;
}
}
int[] results=new int [resultLength];
int index=0;
LruCache cache=new LruCache(k);
for(int[] operator:operators)
{
switch(operator[0])
{
case 1:
cache.set(operator[1],operator[2]);
break;
case 2:
Integer value=cache.get(operator[1]);
results[index++]=value == null?-1:value;
}
}
return results;
}
//之前使用的是LinkedHashMap,底层已经实现好了,但是时间和空间就比较大了
class LruCache{
private final int capacity;
private final Node[] hashTable;
private int nodeCount;
private Node lruHead;
private Node lruTail;
//要删除的节点
private Node eliminateNode;
//构造函数
LruCache(int capacity)
{
if(capacity<1)
{
throw new IllegalArgumentException();
}
this.capacity=capacity;
this.hashTable=new Node[(int)(capacity/0.75)];
}
//set方法
public Integer set(int key,int value)
{
//根据Key获得位置
int index=hashIndex(key);
Node node=hashTable[index];
//这个位置有没有节点
while(node!=null && node.key!=key)
{
node=node.hashLink;
}
//找到了一个key相同的
if(node!=null)
{
int oldValue=node.value;
node.value=value;
touch(node);
return oldValue;
}
if(eliminateNode==null)
{
node=new Node();
}else{
node=eliminateNode;
eliminateNode=null;
}
node.key=key;
node.value=value;
node.hashLink=hashTable[index];
hashTable[index]=node;
if(nodeCount++==0)
{
lruHead=node;
lruTail=node;
}
else{
node.lruPrev=lruTail;
lruTail.lruNext=node;
lruTail=node;
}
if(nodeCount>capacity)
{
eliminate();
}
return null;
}
public Integer get(int key)
{
int index=hashIndex(key);
Node node=hashTable[index];
while(node!=null && node.key!=key)
{
node=node.hashLink;
}
if(node==null)
{
return null;
}
touch(node);
return node.value;
}
//就是按照访问顺序将节点放到了链表的后面
private void touch(Node node){
//是尾结点,直接返回
if(node.lruNext==null)
{
return;
}
//头结点
if(node.lruPrev==null)
{
lruHead=node.lruNext;
lruHead.lruPrev=null;
}else{
node.lruPrev.lruNext=node.lruNext;
node.lruNext.lruPrev=node.lruPrev;
}
node.lruNext=null;
node.lruPrev=lruTail;
lruTail.lruNext=node;
lruTail=node;
}
//删除节点
private void eliminate()
{
Node node=lruHead;
if(node==null)
{
return;
}
//取下第一个节点
lruHead=node.lruNext;
lruHead.lruPrev=null;
node.lruNext=null;
// node 节点就是要删除的节点,这个将在双向链表中删除了节点
//下来需要在桶数组中删除
//获得位置
int index=hashIndex(node.key);
Node cur=hashTable[index];
Node last=null;
while(cur!=null && cur!=node)
{
last=cur;
cur=cur.hashLink;
}
//找到要删除的尾结点,cur要删除的节点,last为前一个节点
//说明node是这个桶数组的第一个节点
if(last==null)
{
hashTable[index]=node.hashLink;
}
else{
last.hashLink=node.hashLink;
}
node.hashLink=null;
eliminateNode=node;
nodeCount--;
}
private int hashIndex(int key)
{
return (key < 0 ? (key == Integer.MIN_VALUE ? 0 : -key) : key) % hashTable.length;
}
}
static class Node{
int key;
int value;
Node hashLink;
Node lruPrev;
Node lruNext;
}
}
方法三:HashMap+链表
上面使用数组比较麻烦,下面使用HashMap
public class LRUCache {
//节点
class Node{
int key;
int value;
Node pre;
Node next;
public Node(){}
public Node(int _key,int _value)
{
key=_key;
value=_value;
}
}
int size;
int capacity;
Map<Integer,Node> map=new HashMap<>();
Node head;
Node tail;
public LRUCache(int capacity)
{
this.capacity=capacity;
this.size=0;
head=new Node();
tail=new Node();
head.next=tail;
tail.pre=head;
}
public int get(int key)
{
Node cur=map.get(key);
if(cur==null)
{
return -1;
}
//移动到链表的头部
moveToHead(cur);
return cur.value;
}
public void moveToHead(Node node)
{
removeNode(node);
moveHead(node);
}
public void removeNode(Node node)
{
node.pre.next=node.next;
node.next.pre=node.pre;
}
public void moveHead(Node node)
{
node.pre=head;
node.next=head.next;
head.next.pre=node;
head.next=node;
}
public void put(int key,int value)
{
Node cur=map.get(key);
if(cur==null)
{
Node node=new Node(key,value);
map.put(key,node);
moveHead(node);
++size;
if(size>capacity)
{
//删除链表末尾
Node delete=reomoveTail();
map.remove(delete.key);
--size;
}
}else
{
cur.value=value;
moveToHead(cur);
}
}
public Node reomoveTail()
{
//这里的tail是一个NUll节点
Node tailP=tail.pre;
removeNode(tailP);
return tailP;
}
}
/**
* 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);
*/