面试腾讯遇到的算法题。
众所周知,LRU本质就是一个哈希表+双向链表的组合数据结构,java中linkedHashMap就是一个实现好了的LRU,其内部实现就是继承HashMap的基本能力,继承HashMap的内部类Node增加前后两个指针形成一个双向链表,在hashMap的get,put操作基础之上维护这个双向链表。
现在需要不使用LinkedHashMap,只使用HashMap的基础上实现一个LRU。代码如下:
public class LRUCache {
private int capacity;
private int size;
Map<Integer,Node> map;
Node head;
Node tail;
public LRUCache(int capacity) {
this.capacity=capacity;
size=0;
map=new HashMap<>(capacity);
head=new Node();
tail=new Node();
head.next=tail;
tail.pre=head;
}
public int get(int key) {
if(map.containsKey(key)) {
Node p=map.get(key);
moveNodeToHead(p);
return p.value;
}
return -1;
}
public void put(int key, int value) {
if(map.containsKey(key)) {
Node p=map.get(key);
p.value=value;
moveNodeToHead(p);
}else {
Node p=new Node(key,value);
map.put(key,p);
addToHead(p);
if(size==capacity){
map.remove(tail.pre.key);
removeNode(tail.pre);
}else {
size++;
}
}
}
private void addToHead(Node node) {
head.next.pre=node;
node.next=head.next;
node.pre=head;
head.next=node;
}
private void removeNode(Node node){
node.pre.next=node.next;
node.next.pre=node.pre;
}
private void moveNodeToHead(Node node) {
removeNode(node);
addToHead(node);
}
}
class Node{
int key;
int value;
Nodepre;
Nodenext;
public Node() {}
public Node(int key,int value) {
this.key=key;
this.value=value;
}
}
总结:
1、自己实现哈希表和双向链表的组合数据结构,不要像LinkedHashMap那样去继承HashMap,直接修改HashMap中Node的结构(如果按照LinkedHashMap源码的思路自己实现就踩大坑了),而是应该将Node结构中的value从直接存储值改为存储链表结点,因此内部Map属性从Map<Integer,Integer> map改为Map <Integer,Node>。
2、双向链表收尾都指向哨兵结点,这样插入删除链表结点时就不需要判断链表为空时首尾结点的特殊处理情况。
3、对于get,put操作,命中时都有将结点移动到表头这样的操作,可以将这块逻辑剥离出来抽象成公共方法。插入新节点和删除新节点时,注意哈希表中也要进行插入删除操作。