不罗嗦背景,写干货。
题目分析:
采用的数据结构:hash + 双向链表。
原因:hashmap可以满足。添加 O(1) 复杂度
双向链表可以满足插入与删除O(1)时间复杂度。
为什么不用单链表? 因为单链表的删除时间复杂度是O(N).
数据结构是:hashmap。 k是int值,v是双链表的节点。
如下:
HashMap<Integer, DLinkedNode> cache = new HashMap<>();
注意泛型的含义。
首先定义双链表:
//定义双链表的节点
class DLinkedNode {
int key, val;
DLinkedNode pre, next;
//无参构造器 伪头尾用
public DLinkedNode() {
}
public DLinkedNode(int key, int val) {
this.key = key;
this.val = val;
}
}
定义初始的变量。
//定义一个hash表。
private HashMap<Integer, DLinkedNode> cache = new HashMap<>();
//定义缓存的容量
private int capacity;
//初始化一个size的用于看当前有几个吧。
private int size;
//定义一个头尾是方便操作链表吧。 伪头部和伪尾部节点
//为啥是私有的
private DLinkedNode head, tail;
使用伪头 尾的目的是为了方便。
利用构造函数初始化:
//构造函数里初始化
public LRUCache(int capacity) {
this.capacity = capacity;
this.size = 0;
//必须要有无参构造的啊
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.pre = head;
}
1.添加的get方法。
1.如果添加的k不存在。
返回-1.
2.如果key存在,返回对应的v。
获取node的目的:
一是为了将其移动到链表最前端,表示最近使用的。
二是为了获取里面的值,进行返回。
public int get(int key) {
//1.key不存在返回-1.
DLinkedNode node = cache.get(key);
//也可以用containskey判断吧??但是这里还要返回node里的值,所以要获得这个节点。
if (node == null) {
return -1;
}
//key存在.先通过hash表定位 再移动到头部
moveToHead(node);
return node.val;
}
移动到头的方法:
//移动某个节点到头部
//这样写为了解耦,逻辑清晰。
private void moveToHead(DLinkedNode node) {
//1.先从链表删除.
//2.头插。
removeNode(node);
addToHead(node);
}
删除节点的方法:
//1.双链表删除节点
private void removeNode(DLinkedNode node) {
//待删除节点的上一个节点指向待删除节点的下一个节点。
node.pre.next = node.next;
//待删除节点的下一个节点指向 待删除节点的上一个节点。
node.next.pre = node.pre;
//还是加上比较好吧!
node.next = null;
node.pre = null;
}
头插的方法:
//2.头插
private void addToHead(DLinkedNode node) {
//
node.pre = head;
node.next = head.next;
head.next.pre = node;
head.next = node;
}
2.添加的put方法。
.1.k存在。
创建新节点,将数值包装进链表节点。
添加到hash表。
添加到头部。
如果容量超过限制,尾部删除。
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
//1.key不存在
if (node == null) {
//创建一个新节点
DLinkedNode newNode = new DLinkedNode(key, value);
//添加到hash表
cache.put(key, newNode);
//添加到头部
addToHead(newNode);
//还有容量限制超过规定的容量要尾部删除
++size;
if (size > capacity) {
//1.删除尾部节点 为什么不和之前的方法合并呢
DLinkedNode last = removeTail();
//2.删除hash表对应的key 如果node里面不包含key就无法删除了啊、
cache.remove(last.key);
//3.容量减去1.
--size;
}
尾部删除的方法:
private DLinkedNode removeTail() {
//获得链表的尾节点
DLinkedNode res = tail.pre;
//删除尾节点
removeNode(res);
return res;
}
2.key存在。
移动到头部 表示最近使用。
返回对应的值。
else {
//2.key存在 修改value
//移动到头部.
node.val = value;
moveToHead(node);
}
完整的代码:
public class LRUCache {
//定义双链表的节点
class DLinkedNode {
int key, val;
DLinkedNode pre, next;
//无参构造器 伪头尾用
public DLinkedNode() {
}
public DLinkedNode(int key, int val) {
this.key = key;
this.val = val;
}
}
//定义一个hash表。
private HashMap<Integer, DLinkedNode> cache = new HashMap<>();
//定义缓存的容量
private int capacity;
//初始化一个size的用于看当前有几个吧。
private int size;
//定义一个头尾是方便操作链表吧。 伪头部和伪尾部节点
//为啥是私有的
private DLinkedNode head, tail;
//构造函数里初始化
public LRUCache(int capacity) {
this.capacity = capacity;
this.size = 0;
//必须要有无参构造的啊
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.pre = head;
}
public int get(int key) {
//1.key不存在返回-1.
DLinkedNode node = cache.get(key);
//也可以用containskey判断吧??但是这里还要返回node里的值,所以要获得这个节点。
if (node == null) {
return -1;
}
//key存在.先通过hash表定位 再移动到头部
moveToHead(node);
return node.val;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
//1.key不存在
if (node == null) {
//创建一个新节点
DLinkedNode newNode = new DLinkedNode(key, value);
//添加到hash表
cache.put(key, newNode);
//添加到头部
addToHead(newNode);
//还有容量限制超过规定的容量要尾部删除
++size;
if (size > capacity) {
//1.删除尾部节点 为什么不和之前的方法合并呢
DLinkedNode last = removeTail();
//2.删除hash表对应的key 如果node里面不包含key就无法删除了啊、
cache.remove(last.key);
//3.容量减去1.
--size;
}
} else {
//2.key存在 修改value
//移动到头部.
node.val = value;
moveToHead(node);
}
}
//还是去调用之前的了。牛啊
private DLinkedNode removeTail() {
//获得链表的尾节点
DLinkedNode res = tail.pre;
//删除尾节点
removeNode(res);
return res;
}
//移动某个节点到头部
private void moveToHead(DLinkedNode node) {
//1.先从链表删除.
//2.头插。
removeNode(node);
addToHead(node);
}
//1.双链表删除节点
private void removeNode(DLinkedNode node) {
//待删除节点的上一个节点指向待删除节点的下一个节点。
node.pre.next = node.next;
//待删除节点的下一个节点指向 待删除节点的上一个节点。
node.next.pre = node.pre;
//还是加上比较好吧!
node.next = null;
node.pre = null;
}
//2.头插
private void addToHead(DLinkedNode node) {
//
node.pre = head;
node.next = head.next;
head.next.pre = node;
head.next = node;
}
}
运行结果: