目录
题目
思路
- 看题意字面意思应该还是理解的,但是我记得要用双向链表+HashMap什么的,所以应该不像题目看起来这么简单,直接润去题解。
- 对于已经构建好的LRU缓存,操作解释如下
//创建缓存长度为2的LRU缓存机制
LRUCache cache = new LRUCache(2);
// 可以把 cache 理解成一个队列
// 假设左边是队头,右边是队尾
// 最近使用的排在队头,久未使用的排在队尾
// 圆括号表示键值对 (key, val)
cache.put(1, 1);
// cache = [(1, 1)]
cache.put(2, 2);
// cache = [(2, 2), (1, 1)]
cache.get(1); // 返回 1
// cache = [(1, 1), (2, 2)]
// 解释:因为最近访问了键 1,所以提前至队头
// 返回键 1 对应的值 1
cache.put(3, 3);
// cache = [(3, 3), (1, 1)]
// 解释:缓存容量已满,需要删除内容空出位置
// 优先删除久未使用的数据,也就是队尾的数据
// 然后把新的数据插入队头
cache.get(2); // 返回 -1 (未找到)
// cache = [(3, 3), (1, 1)]
// 解释:cache 中不存在键为 2 的数据
cache.put(1, 4);
// cache = [(1, 4), (3, 3)]
// 解释:键 1 已存在,把原始值 1 覆盖为 4
// 不要忘了也要将键值对提前到队头
- 可以看出来,比起算法,这道题意思更是让我们写数据结构,其中get和put都必须以O(1)的平均时间复杂度运行。
- 很明显,在插入删除上,可以考虑使用双向链表,而在数据的保存和查找上,可以考虑使用哈希表查找。结合前面两点,形成一个新的数据结构:哈希链表。
- 必须选择双向链表的原因是我们经常需要进行动态的删除,此时必须知道节点前驱的消息,所以双向链表会方便一点。
思路代码
双向链表Node节点
//创建双链表的节点类
class Node{
public int key,val; //kay和value
public Node next,prev; //前后两节点
public Node(int k,int v){//创建函数
this.key=k;
this.val=v;
}
}
根据节点创建双向链表
注意里面只写了从尾部添加的方法,所以默认为靠近尾部的为较新的,靠近头部的为较久的
//根据节点,创建双链表
class DoubleLink{
//头尾虚节点 至于为什么需要虚节点 推测应该是因为头尾经常需要互换 所以使用虚节点方便计算
private Node head,tail;
//链表元素数
private int size;
//构造函数
public DoubleLink(){
//初始化双向链表的数据 头尾节点 和size
head = new Node(0,0);
tail = new Node(0,0);
head.next=tail;
tail.prev=head;
size=0;
}
//在链表尾部添加节点 时间复杂度O(1)
public void addLast(Node x){
x.prev = tail.prev;
x.next=tail;
tail.prev.next=x;
tail.prev=x;
size++;
}
//删除链表中的元素 因为之前会用hashmap查询 所以x一定存在
public void remove(Node x){
x.next.prev = x.prev;
x.prev.next = x.next;
size--;
}
//删除链表的第一个节点,并返回该节点
public Node removeFirst(){
//注意特殊情况
if(head.next==tail) return null;
// head.next.next.prev = head;
// head.next=head.next.next;
// size--;
//好像可以直接调用 不过像我上面这样写应该也可以?
Node temp = head.next;
remove(temp);
return head;
}
//返回链表的size
public int size() { return size; }
}
结合双向链表和HashMap创建LRUCache
class LRUCache {
//哈希表 注意一下 哈希表需要和链表链接起来,因为链表可以存储key value 而查找的时候只需要知道key
//所以哈希表定为<key Node>
private HashMap<Integer,Node>map;
//双向链表 存储具体数据
private DoubleLink cache;
//最大容量
private int cap;
public LRUCache(int capacity) {
//创建函数时很明显 创建对应的哈希表 双向链表 设定容量
this.cap = capacity;
this.map = new HashMap<Integer,Node>();
this.cache = new DoubleLink();
}
}
写几个避免get和put函数直接操作双向链表和map的辅助函数
- 将key对应的节点提升为最近使用的节点
- 添加新元素
- 删除key
- 删除最久没使用的元素
//将key对应的节点提升为最近使用的节点
private void MakeRecently(int key){
Node x = map.get(key);
//从链表中删除这个节点 然后把这个节点重新加入队尾
cache.remove(x);
cache.addLast(x);
}
//添加新元素
private void addRencently(int key,int val){
//直接创建node
Node x = new Node(key,val);
//put进map
map.put(key,x);
//在cache里进行修改
cache.addLast(x);
}
//删除一个key
private void DeleteKey(int key){
//获取对应的node
Node x = map.get(key);
//哈希表里面删除
map.remove(key);
//cache里删除
cache.remove(x);
}
//删除最久没使用的元素
private void removeLastRecently(){
//要先获取被cache删除的节点 需要在map里删除
Node deleteNode = cache.removeFirst();
//移除cache里的最后一个即可
map.remove(deleteNode.key);
}
get()和put()
//题目要求为如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
public int get(int key) {
if(map.containsKey(key)){
//首先提到最近使用的节点
MakeRecently(key);
return map.get(key).val;
}else return -1;
}
//题目要求为如果关键字key已经存在,则变更其数据值为value
// 如果不存在,则向缓存中插入该组key-value
// 如果插入操作导致关键字数量超过capacity ,则应该 逐出 最久未使用的关键字。
public void put(int key, int value) {
//如果关键字存在
if(map.containsKey(key)){
//删除旧元素 添加新元素 因为写那个方法的时候没有判断重复 所以必须让它不重复
DeleteKey(key);
addRencently(key,value);
}else { //判断当前cap和总容量的关系
if(cache.size()==this.cap){
removeLastRecently();
}
//加入
addRencently(key,value);
}
}
总代码
import java.util.HashMap;
//创建双链表的节点类
class Node{
public int key,val; //kay和value
public Node next,prev; //前后两节点
public Node(int k,int v){//创建函数
this.key=k;
this.val=v;
}
}
//根据节点,创建双链表
class DoubleLink{
//头尾虚节点 至于为什么需要虚节点 推测应该是因为头尾经常需要互换 所以使用虚节点方便计算
private Node head,tail;
//链表元素数
private int size;
//构造函数
public DoubleLink(){
//初始化双向链表的数据 头尾节点 和size
head = new Node(0,0);
tail = new Node(0,0);
head.next=tail;
tail.prev=head;
size=0;
}
//在链表尾部添加节点 时间复杂度O(1)
public void addLast(Node x){
x.prev = tail.prev;
x.next=tail;
tail.prev.next=x;
tail.prev=x;
size++;
}
//删除链表中的元素 因为之前会用hashmap查询 所以x一定存在
public void remove(Node x){
x.next.prev = x.prev;
x.prev.next = x.next;
size--;
}
//删除链表的第一个节点,并返回删除的那个节点
public Node removeFirst(){
//注意特殊情况
if(head.next==tail) return null;
// head.next.next.prev = head;
// head.next=head.next.next;
// size--;
//好像可以直接调用 不过像我上面这样写应该也可以?
Node temp = head.next;
remove(temp);
return temp;
}
//返回链表的size
public int size() { return size; }
}
public class LRUCache {
//哈希表 注意一下 哈希表需要和链表链接起来,因为链表可以存储key value 而查找的时候只需要知道key
//所以哈希表定为<key Node>
private HashMap<Integer,Node>map;
//双向链表 存储具体数据
private DoubleLink cache;
//最大容量
private int cap;
public LRUCache(int capacity) {
//创建函数时很明显 创建对应的哈希表 双向链表 设定容量
this.cap = capacity;
this.map = new HashMap<Integer,Node>();
this.cache = new DoubleLink();
}
//将key对应的节点提升为最近使用的节点
private void MakeRecently(int key){
Node x = map.get(key);
//从链表中删除这个节点 然后把这个节点重新加入队尾
cache.remove(x);
cache.addLast(x);
}
//添加新元素
private void addRencently(int key,int val){
//直接创建node
Node x = new Node(key,val);
//put进map
map.put(key,x);
//在cache里进行修改
cache.addLast(x);
}
//删除一个key
private void DeleteKey(int key){
//获取对应的node
Node x = map.get(key);
//哈希表里面删除
map.remove(key);
//cache里删除
cache.remove(x);
}
//删除最久没使用的元素
private void removeLastRecently(){
//要先获取被cache删除的节点 需要在map里删除
Node deleteNode = cache.removeFirst();
//移除cache里的最后一个即可
map.remove(deleteNode.key);
}
//题目要求为如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
public int get(int key) {
if(map.containsKey(key)){
//首先提到最近使用的节点
MakeRecently(key);
return map.get(key).val;
}else return -1;
}
//题目要求为如果关键字key已经存在,则变更其数据值为value
// 如果不存在,则向缓存中插入该组key-value
// 如果插入操作导致关键字数量超过capacity ,则应该 逐出 最久未使用的关键字。
public void put(int key, int value) {
//如果关键字存在
if(map.containsKey(key)){
//删除旧元素 添加新元素 因为写那个方法的时候没有判断重复 所以必须让它不重复
DeleteKey(key);
addRencently(key,value);
}else { //判断当前cap和总容量的关系
if(cache.size()==this.cap){
removeLastRecently();
}
//加入
addRencently(key,value);
}
}
}