一次倒在LRU上的经历(二)
哈希+双链表
上面我们已经知道用哈希能够直接查到有木有这个元素,但是苦于删除!用List都很费力。
更详细的说,是苦于List的删除操作,Map的删除插入还是很高效的。
在上面这种情况,我们希望的就是能够快速删除List中任意一个元素,并且效率很高,如果借助哈希只能最多定位到,但是无法删除啊!该怎么办呢?
哈希+双链表啊!
我们将key-val的数据存到一个Node类中,然后每个Node知道左右节点,在插入链表的时候直接存入Map中,这样Map在查询的时候可以直接返回该节点,双链表知道左右节点可以直接将该节点在双链表中删除。
当然,为了效率,这里实现的双链表带头结点(头指针指向一个空节点防止删除等异常情况)和尾指针。
对于这个情况,你需要能够手写链表和双链表啦,双链表的增删改查已经写过清清楚楚,小伙伴们不要担心,这里我已经整理好啦:
单链表:https://mp.weixin.qq.com/s/Cq98GmXt61-2wFj4WWezSg
双链表:https://mp.weixin.qq.com/s/h6s7lXt5G3JdkBZTi01G3A
也就是你可以通过HashMap直接得到在双链表中对应的Node,然后根据前后节点关系删除,期间要考虑的一些null、尾指针删除等等特殊情况即可。
具体实现的代码为:
class LRUCache {
class Node {
int key;
int value;
Node pre;
Node next;
public Node() {
}
public Node( int key,int value) {
this.key = key;
this.value=value;
}
}
class DoubleList{
private Node head;// 头节点
private Node tail;// 尾节点
private int length;
public DoubleList() {
head = new Node(-1,-1);
tail = head;
length = 0;
}
void add(Node teamNode)// 默认尾节点插入
{
tail.next = teamNode;
teamNode.pre=tail;
tail = teamNode;
length++;
}
void deleteFirst(){
if(head.next==null)
return;
if(head.next==tail)//如果删除的那个刚好是tail 注意啦 tail指针前面移动
tail=head;
head.next=head.next.next;
if(head.next!=null)
head.next.pre=head;
length--;
}
void deleteNode(Node team){
team.pre.next=team.next;
if(team.next!=null)
team.next.pre=team.pre;
if(team==tail)
tail=tail.pre;
team.pre=null;
team.next=null;
length--;
}
public String toString() {
Node team = head.next;
String vaString = "len:"+length+" ";
while (team != null) {
vaString +="key:"+team.key+" val:"+ team.value + " ";
team = team.next;
}
return vaString;
}
}
Map<Integer,Node> map=new HashMap<>();
DoubleList doubleList;//存储顺序
int maxSize;
LinkedList<Integer>list2=new LinkedList<>();
public LRUCache(int capacity) {
doubleList=new DoubleList();
maxSize=capacity;
}
public void print(){
System.out.print("maplen:"+map.keySet().size()+" ");
for(Integer in:map.keySet()){
System.out.print("key:"+in+" val:"+map.get(in).value+" ");
}
System.out.print(" ");
System.out.println("listLen:"+doubleList.length+" "+doubleList.toString()+" maxSize:"+maxSize);
}
public int get(int key) {
int val;
if(!map.containsKey(key))
return -1;
val=map.get(key).value;
Node team=map.get(key);
doubleList.deleteNode(team);
doubleList.add(team);
return val;
}
public void put(int key, int value) {
if(map.containsKey(key)){// 已经有这个key 不考虑长短直接删除然后更新
Node deleteNode=map.get(key);
doubleList.deleteNode(deleteNode);
}
else if(doubleList.length==maxSize){//不包含并且长度小于
Node first=doubleList.head.next;
map.remove(first.key);
doubleList.deleteFirst();
}
Node node=new Node(key,value);
doubleList.add(node);
map.put(key,node);
}
}
就这样,一个get和put都是O(1)复杂度的LRU写出来啦!
尾声
后来看了题解,才发现,Java中的LinkedHashMap也差不多是这种数据结构!几行解决,但是一般面试官可能不会认同,还是会希望大家能够手写一个双链表的。
class LRUCache extends LinkedHashMap<Integer, Integer>{
private int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75F, true);
this.capacity = capacity;
}
public int get(int key) {
return super.getOrDefault(key, -1);
}
public void put(int key, int value) {
super.put(key, value);
}
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > capacity;
}
}//这个来源官方题解 给大家分享一下
哈希+双链表虽然在未看题解的情况想出来,但是真的花了挺久才想到这个点,以前见得确实比较少,高效手写LRU到今天算是真真正正的完全掌握啦!
不过除了LRU,其他的页面置换算法无论笔试还是面试也是非常高频啊,大家有空自己梳理一下哦。
除了算法水平真的很强的,不然大部分人其实不可能当场想到一些比较巧妙的方法,所以面对笔试最好的诀窍还是多刷力扣。
文章转自公众号:bigsai