每日LeeteCode-实现LRU cache
题目描述
实现一个K-V的数据结构, key和value都是int类型,value为正整数
1.有固定的大小
2.实现get(), set()方法,要求时间复杂度为O(1)
3.当容量满的时候,采用LRU淘汰策略
面试的时候拿到这道题还是挺懵的无从下手,没有在规定时间内做出来.现在回过头来想,其实要求给的蛮详细的,只要冷静下来分析下,还是挺简单的
题目分析
定义一个类来实现这个数据结构: LRUCache
有固定大小?
既然给了固定大小,就需要有属性来存储这个值,并且是不可变的
private final int capacity;
面试官给的是300的固定值,在这我通过构造函数的方式将数值填入
LRUCache(int capacity) {
this.capacity = capacity;
}
此外, 我们还需要定义一个属性来存储当前的存储个数
private final int count;
实现get(), set()方法,要求时间复杂度为O(1)?
要求是K-V结构, 有要求get,set都是O(1), 这种典型的数据结构,自然而然想到了HashMap, 那么我们暂且使用HashMap来存储数据吧
private Map<Integer, Integer> map = HashMap<>();
当容量满的时候,采用LRU淘汰策略
这个就有些难度了, 首先我们来了解下什么事LRU淘汰策略:
1.淘汰最少使用的数据
2.淘汰最早放进来的数据
为了实现这个功能,我使用双向链表的数据结构实现:
1.定义空节点为head和tail -》卡左右边界,减少为空判断
2.新写入的节点插入到head后面
3.如果某个数据被get了,就将这个节点从原位置移动到head后面
4.如果容量满了, 删除尾节点,将前一个节点设置成尾节点,清空新的尾节点数据,达到删除数据的效果
申明Node类来存储节点数据:
class Node {
public int key; // key
public int value; // value
public Node prev; // 前置节点
public Node next; // 后置节点
}
在LRUCache中申明双向链表的head和tail节点,并在构造函数中进行初始化操作:
private Node head;
private Node tail;
public LRUCache(int capacity) {
this.capacity = capacity; // 第一步的容量设定
head = new Node();
tail = new Node();
head.next = tail; // 头节点和尾节点链接起来,node节点get的时候插入进来就行
tail.prev = head;
}
HashMap和双向链表如何绑定?
通过以上分析后, 我们在LRUCache类里面申明了两种数据结构: HashMap和双向链表,一个负责get.set的O(1)效率,一个负责LRU淘汰策略的实现,那我们又该如何将两者结合起来呢?
这里, 我们修改下HashMap的申明, key不变,将它的value改为Node,这样,我们就通过Node作为中间介质将两者联系到一起啦
private Map<Integer, Node> map = new HashMap<>();
代码实现
对上述分析进行整合,完整地实现LRUCache
public class LRUCache {
private static class Node {
public int key;
public int value;
public Node prev;
public Node next;
public Node() {
this.key = -1;
this.value = -1;
this.prev = null;
this.next = null;
}
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
private final int capacity;
private Map<Integer, Node> map = new HashMap<>();
private Node head;
private Node tail;
private int count = 0;
public LRUCache(int capacity) {
this.capacity = capacity;
head = new Node();
tail = new Node();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
Node node = map.get(key);
if (node == null) {
return -1;
}
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
// key存在,对旧值覆盖
if (map.containsKey(key)) {
Node node = map.get(key);
node.value = value;
moveToHead(node);
return;
}
// 内存已满 删除最后一个节点再添加
if (count == capacity) {
removeLast();
addNewNode(key, value);
return;
}
// 添加新节点,当前内存数+1
addNewNode(key, value);
count++;
}
/**
* 添加新节点
*/
private void addNewNode(int key, int value) {
Node node = new Node(key, value);
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
map.put(key, node);
}
/**
* 将节点移动到head
*/
private void moveToHead(Node node) {
if (head.next != node) {
node.prev.next = node.next;
node.next.prev = node.prev;
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
}
/**
* 删除最后一个节点
*/
private void removeLast() {
tail = tail.prev;
tail.value = -1;
tail.next = null;
map.remove(tail.key);
}
}
最后, 来看下实际效果吧!
public class Test {
public static void main(String[] args) {
LRUCache lruCache = new LRUCache(2);
lruCache.put(1, 1);
lruCache.put(2, 2);
System.out.println(lruCache.get(1));
lruCache.put(3, 3);
System.out.println(lruCache.get(2));
lruCache.put(4, 4);
System.out.println(lruCache.get(1));
System.out.println(lruCache.get(3));
System.out.println(lruCache.get(4));
}
}
如果想更好地验证自己的代码是否成功,可以在leeteCode上进行调试
调试链接: https://leetcode-cn.com/problems/lru-cache/
本文地址:https://blog.csdn.net/weixin_43188031/article/details/107590420
上一篇: 建军节吃什么食物?建军节的由来是什么?
下一篇: CRUD是什么,程序猿都应该要知道的单词