设计LRU缓存结构
描述
设计LRU缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能
- set(key, value):将记录(key, value)插入该结构
- get(key):返回key对应的value值
[要求]
- set和get方法的时间复杂度为O(1)
- 某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的。
- 当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。
思路:
我们想要达到插入和获取都是O(1)的时间复杂度的话,那么我们需要借助双向链表和Hash表。
- 我们插入数据的时候同时插入链表和hash表,和将该节点插入到链表头
- 获取值的时候从hash表获取,将该节点移动到链表头
- 当插入数据的时候发现链表长度已经达到预定容量,则删除末尾节点
代码:
import java.util.*;
public class Solution {
static class LRUCache<K, V> {
public class Entry {
K key;
V value;
Entry prev;
Entry next;
public Entry() {
}
public Entry(K key, V value) {
this.key = key;
this.value = value;
}
}
/**
* 虚拟头节点
*/
private Entry head;
/**
* 虚拟尾节点
*/
private Entry tail;
/**
* 链表长度
*/
private int size;
/**
* 默认容量
*/
private static final int DEFAULT_CAP = 10;
/**
* 容量
*/
private int cap;
private HashMap<K, Entry> cache;
public LRUCache(int cap) {
this.size = 0;
this.cap = cap;
this.cache = new HashMap<>(this.cap);
head = new Entry();
tail = new Entry();
//将虚拟尾连接到头节点上,就不用管他了,一切操作在这个之间操作
head.next = tail;
}
public LRUCache() {
this.size = 0;
this.cap = DEFAULT_CAP;
this.cache = new HashMap<>(cap);
head = new Entry();
tail = new Entry();
head.next = tail;
}
public V get(K key) {
Entry entry = cache.get(key);
if (entry != null) {
//如果存在,则移动到链表头
moveToHead(entry);
return entry.value;
}
return null;
}
public void set(K key, V value) {
Entry entry = cache.get(key);
if (entry != null) {
//更新值
entry.value = value;
//移动到头
moveToHead(entry);
//添加到缓存
cache.put(key, entry);
} else {
Entry newEntry = new Entry(key, value);
cache.put(key, newEntry);
//往头部添加
addToHead(newEntry);
size++;
//边界问题
if (size > cap) {
//删除尾链表
cache.remove(tail.prev.key);
removeTail(tail.prev);
size--;
}
}
}
private void removeTail(Entry entry) {
//删除节点
removeNode(entry);
}
private void moveToHead(Entry entry) {
//删除节点
removeNode(entry);
//移动到头部
addToHead(entry);
}
private void addToHead(Entry entry) {
//将当前节点的下一个节点指向头节点的下一个节点
entry.next = head.next;
//将当前节点的prev 指向虚拟头节点
entry.prev = head;
//接下来处理head的下一个节点,将head的下一个节点的prev指针指向添加节点
head.next.prev = entry;
//更新值
head.next = entry;
}
private void removeNode(Entry entry) {
//断开
entry.prev.next = entry.next;
entry.next.prev = entry.prev;
}
}
}
测试:
public static void main(String[] args) {
Solution solution = new Solution();
int[][] arr = new int[6][];
arr[0] = new int[]{1, 1, 1};
arr[1] = new int[]{1, 2, 2};
arr[2] = new int[]{1, 3, 2};
arr[3] = new int[]{2, 1};
arr[4] = new int[]{1, 4, 4};
arr[5] = new int[]{2, 2};
int[] lru = solution.LRU(arr, 3);
System.out.println(Arrays.toString(lru));
}
/**
* lru design
*
* @param operators int整型二维数组 the ops
* @param k int整型 the k
* @return int整型一维数组
*/
public int[] LRU(int[][] operators, int k) {
// write code here
int resultLength = 0;
for (int[] operator : operators) {
if (operator[0] == 2) {
resultLength++;
}
}
int[] results = new int[resultLength];
int index = 0;
LRUCache<Integer, Integer> cache = new LRUCache(k);
for (int[] operator : operators) {
switch (operator[0]) {
case 1:
cache.set(operator[1], operator[2]);
break;
case 2:
results[index++] = cache.get(operator[1]) == null ? -1 : cache.get(operator[1]);
}
}
return results;
}