仅用单链表、单链表加hashMap、双向链表加hashMap、LinkedHashMap实现LRU
leetCode题目表述 题目理解会有一个误区:这里的“最近”指是最近时间以内是否访问过,而非距离最近。
解体思路:
首先涉及元素频繁的增删,使用链表。根据题意,可维护一个队列,将最近访问过的元素(get、put操作)放在队列尾部,相反最久时间没访问的元素的位置则都在队列头。
get操作:如果能找到该元素(使用hashmap寻找或遍历链表寻找),则将其移动到队列尾部,返回其value值。否则返回-1;
put操作:如果能找到该元素(使用hashmap寻找或遍历链表寻找),则将其移动到队列尾部,并更新value值即可。
否则将其添加到队列尾部。此时判断如果size小于容量capacity则正常退出即可。如果size大于容量capacity则删除队列的头元素,即删除最久未使用的元素。
一.仅用单链表
class LRUCache {
Node head;
int capacity;
int length;
public LRUCache(int capacity) {
this.capacity = capacity;
this.length = 0;
}
public int get(int key) {
Node findNode = findNodeByKey(key);
if(findNode!=null) {
//从原来位置删除
deleteData(key);
//从头添加
addAtHead(key,findNode.value);
return findNode.value;
}
return -1;
}
public void put(int key, int value) {
//已经存在key,则更新
Node findNode = findNodeByKey(key);
if(findNode!=null) {
//从原来位置删除
deleteData(key);
//从头添加
addAtHead(key,value);
} else {//key不存在,则添加
//未装满添加
if(length<capacity){
//从头添加
addAtHead(key,value);
} else {//装满添加
//删除最后一个元素
deleteAtTail();
//从头添加
addAtHead(key,value);
}
}
}
//查找目标节点
public Node findNodeByKey(int key){
if(head == null)
return null;
Node cur = head;
while(cur!=null) {
if(key==cur.key){
return cur;
}
cur = cur.next;
}
return null;
}
//从头添加
public void addAtHead(int key,int value){
Node newNode = new Node(key,value);
if(head == null){
head = newNode;
} else {
newNode.next = head;
head = newNode;
}
length++;
}
//删除最后一个元素
public void deleteAtTail(){
Node cur = head;
if(cur == null) {
return ;
}
Node next=cur.next;
if(next==null){
head=null;
length--;
return;
}
while(next.next!=null)
{
cur=next;
next=next.next;
}
cur.next=null;
length--;
}
//删除指定key
public void deleteData(int key){
if(key==head.key){
head = head.next;
length--;
return;
} else {
Node cur = head;
Node pre = null;
while(cur!=null){
if(key==cur.key){
pre.next = cur.next;
cur.next = null;
length--;
return;
}
pre = cur;
cur = cur.next;
}
}
}
class Node {
int key;
int value;
Node next;
Node(int key,int value){
this.key = key;
this.value = value;
next = null;
}
}
}
二.单链表加hashMap
这里为了方便删除操作,在map中不存当前节点,而是存当前节点的前驱。 另外使用了哨兵(带头节点)
public class LRUCache{
private class ListNode {
int key, val;
ListNode next;
public ListNode(int key, int val) {
this.key = key;
this.val = val;
this.next = null;
}
}
private int capacity;
private Map<Integer, ListNode> map; //key-> node.pre
private ListNode head; //dummy
private ListNode tail;
public LRUCache(int capacity) {
this.capacity = capacity;
map = new HashMap<>();
head = new ListNode(-1, -1);
tail = head;
}
public int get(int key) {
if (!map.containsKey(key)) {
return -1;
}
//map中存放的是要找的节点的前驱
ListNode pre = map.get(key);
ListNode cur = pre.next;
//把当前节点删掉并移到尾部
if (cur != tail) {
pre.next = cur.next;
map.put(cur.next.key, pre); //更新它后面node的前驱
map.put(cur.key, tail);
moveToTail(cur);
}
return cur.val;
}
public void put(int key, int value) {
if (get(key) != -1) {
map.get(key).next.val = value;
return;
}
//不存在就new一个
ListNode node = new ListNode(key, value);
map.put(key, tail); //当前node的pre是tail
moveToTail(node);
if (map.size() > capacity) {
map.remove(head.next.key);
map.put(head.next.next.key, head);
head.next = head.next.next;
}
}
private void moveToTail(ListNode node) {
node.next = null;
tail.next = node;
tail = tail.next;
}
}
作者:jerry_nju
链接:https://leetcode-cn.com/problems/lru-cache-lcci/solution/linkedhashmap-shuang-lian-biao-hashmap-dan-lian-2/
来源:力扣(LeetCode)
三.双向链表加hashMap
考虑删除操作,要把当前节点的前一个节点的指针的改变,获取它前一个节点,方便的数据结构就是 双向链表,这里使用了哨兵(头尾都使用了空节点)
class LRUCache {
int capacity;
Map<Integer,Node> map = new HashMap<>();
Node head ;
Node tail;
public LRUCache(int capacity) {
this.capacity = capacity;
head = new Node(-1, -1);
tail = new Node(-1, -1);
head.next=tail;
tail.pre=head;
}
public int get(int key) {
//hashmap中是否有这个元素,如果没有直接返回-1
if(!map.containsKey(key))
return-1;
//如果有,则将这个元素放到链表的尾部
Node cur = map.get(key);
//删除这个元素
cur.pre.next = cur.next;
cur.next.pre = cur.pre;
//放到链表的尾部
moveToTail(cur);
return cur.value;
}
public void put(int key, int value) {
//直接调用这边的get方法,如果存在,它会在get内部被移动到尾巴,不用再移动一遍,直接修改值即可
if(get(key)!=-1) {
map.get(key).value = value;
return;
}
//如果不存在则添加到尾部
Node newNode = new Node(key,value);
map.put(key,newNode);
moveToTail(newNode);
//如果此时超出容量,则从头删去一个旧元素
if(map.size()>capacity) {
map.remove(head.next.key);
head.next = head.next.next;
head.next.pre = head;
}
}
private void moveToTail(Node cur) {
tail.pre.next = cur;
cur.next = tail;
cur.pre = tail.pre;
tail.pre = cur;
}
class Node {
int key;
int value;
Node pre;
Node next;
Node(int key,int value){
this.key = key;
this.value = value;
this.pre = null;
this.next = null;
}
}
}
四.LinkedHashMap
使用LinkedHashMap的accessOrder模式
public class LRUCache
{
int capacity;
Map<Integer, Integer> map;
public LRUCache(int capacity) {
this.capacity = capacity;
map = new LinkedHashMap<>((int)((float)capacity / 0.75f + 1.0f), 0.75f, true);
}
public int get(int key)
{
Integer value = map.get(key);
if (value == null)
return -1;
return value;
}
public void put(int key, int value) {
map.put(key, value);
if (map.size() > capacity) {
//超出capacity,删除最久没用的,利用迭代器,删除第一个
map.remove(map.entrySet().iterator().next().getKey());
}
}
}
总结:解题使用的数据结构可以是 LinkedList(底层是双向链表)+ HashMap,而实际上可以直接用LinkedHashMap更为方便。看面试官要求是啥了。