https://leetcode-cn.com/problems/lru-cache/
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
实现 LRUCache
类:
LRUCache(int capacity)
以正整数作为容量capacity
初始化 LRU 缓存int get(int key)
如果关键字key
存在于缓存中,则返回关键字的值,否则返回 -1 。void put(int key, int value)
如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:你是否可以在 O(1)
时间复杂度内完成这两种操作?
示例:
输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]
解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4
- 确定需要使用的数据结构
- 根据题目要求,存储的数据需要保证顺序关系(逻辑层面) ===> 使用数组,链表等保证顺序关系
- 同时需要对数据进行频繁的增删, 时间复杂度 O(1) ==> 使用链表等
- 对数据进行读取时, 时间复杂度 O(1) ===> 使用哈希表
最终采取双向链表 + 哈希表
- 双向链表按最后一次访问的时间的顺序进行排列, 链表头部为最近访问的节点
- 哈希表,以关键字为键,以链表节点的地址为值
- put 操作
通过哈希表, 查看传入的关键字对应的链表节点, 是否存在
- 如果存在,
- 将该链表节点的值更新
- 将该该链表节点调整至链表头部
- 如果不存在
- 如果链表容量未满,
- 新生成节点,
- 将该节点位置调整至链表头部
- 如果链表容量已满
- 删除尾部节点
- 新生成节点
- 将该节点位置调整至链表头部
- 将新生成的节点,按关键字为键,节点地址为值插入哈希表
- 如果链表容量未满,
- 如果存在,
- get 操作
通过哈希表, 查看传入的关键字对应的链表节点, 是否存在
- 节点存在
- 将该节点位置调整至链表头部
- 返回该节点的值
- 节点不存在, 返回 null
- 节点存在
伪代码如下:
var LRUCache = function(capacity) {
保存一个该数据结构的最大容量
生成一个双向链表,同时保存该链表的头结点与尾节点
生成一个哈希表
};
function get (key) {
if 哈希表中存在该关键字 {
根据哈希表获取该链表节点
将该节点放置于链表头部
return 链表节点的值
} else {
return -1
}
};
function put (key, value) {
if 哈希表中存在该关键字 {
根据哈希表获取该链表节点
将该链表节点的值更新
将该节点放置于链表头部
} else {
if 容量已满 {
删除链表尾部的节点
新生成一个节点
将该节点放置于链表头部
} else {
新生成一个节点
将该节点放置于链表头部
}
}
};
语言支持: JS, Go, PHP, Python3
JS Code:
class ListNode{
constructor(key, val){
this.key = key;
this.val = val;
this.pre = null;
this.next = null;
}
};
class LRUCache{
constructor(capacity){
this.capacity = capacity;
this.size = 0;
this.data = {};
this.head = new ListNode();
this.tail = new ListNode();
this.head.next = this.tail;
this.tail.pre = this.head;
}
get(key){
if(!this.data[key]) return -1;
else{
let node = this.data[key];
this.removeNode(node);
this.appendHead(node);
return node.val;
}
}
put(key, value){
if(!this.data[key]){
let node = new ListNode(key, value);
this.data[key] = node;
this.appendHead(node);
this.size++;
if(this.size > this.capacity){
const lastKey = this.removeTail();
delete this.data[lastKey];
this.size--;
}
}else{
let node = this.data[key];
this.removeNode(node);
node.val = value;
this.appendHead(node);
}
}
removeNode(node){
let preNode = node.pre;
let nextNode = node.next;
preNode.next = nextNode;
nextNode.pre = preNode;
}
appendHead(node){
let firstNode = this.head.next;
this.head.next = node;
node.pre = this.head;
node.next = firstNode;
firstNode.pre = node;
}
removeTail(){
let key = this.tail.pre.key;
this.removeNode(this.tail.pre);
return key;
}
}
Go Code:
type LRUCache struct {
Cap int
Hash map[int]*DoubleListNode
Cache *DoubleList
Size int
}
func Constructor(capacity int) LRUCache {
return LRUCache{
Cap: capacity,
Hash: make(map[int]*DoubleListNode),
Cache: NewDoubleList(),
}
}
func (this *LRUCache) Get(key int) int {
n, ok := this.Hash[key]
if !ok {
return -1
}
// 设为最近使用过
this.Cache.remove(n)
this.Cache.append(n)
return n.Val
}
func (this *LRUCache) Put(key int, value int) {
n, ok := this.Hash[key]
if !ok { // cache 不存在
// 增加节点
newNode := &DoubleListNode{Key: key, Val: value}
this.Hash[key] = newNode
this.Cache.append(newNode)
if this.Cap == this.Size { // 已满
// 删除尾节点
tailNode := this.Cache.tail.Pre
delete(this.Hash, tailNode.Key)
this.Cache.remove(tailNode)
} else {
this.Size++
}
} else {
// cache 存在
// 设为最近使用过
this.Cache.remove(n)
this.Cache.append(n)
n.Val = value // 更新值
}
}
type DoubleListNode struct {
Key, Val int
Pre, Next *DoubleListNode
}
type DoubleList struct {
Head, tail *DoubleListNode
}
func NewDoubleList() *DoubleList {
dl := &DoubleList{
Head: &DoubleListNode{},
tail: &DoubleListNode{},
}
dl.Head.Next = dl.tail
dl.tail.Pre = dl.Head
return dl
}
func (dl *DoubleList) append(n *DoubleListNode) {
n.Next = dl.Head.Next
n.Pre = dl.Head
dl.Head.Next.Pre = n
dl.Head.Next = n
}
func (dl *DoubleList) remove(n *DoubleListNode) {
n.Pre.Next = n.Next
n.Next.Pre = n.Pre
}
PHP Code:
class LRUCache
{
public $hash = []; // key => DoubleListNode
public $cache;
public $size = 0;
public $cap; // 容量
/**
* @param Integer $capacity
*/
function __construct($capacity)
{
$this->cap = $capacity;
$this->cache = new DoubleList();
}
/**
* @param Integer $key
* @return Integer
*/
function get($key)
{
if (!isset($this->hash[$key])) return -1;
// 更新节点
/** @var DoubleListNode $node */
$node = $this->hash[$key];
$this->cache->remove($node);
$this->cache->append($node);
return $node->val;
}
/**
* @param Integer $key
* @param Integer $value
* @return NULL
*/
function put($key, $value)
{
if (isset($this->hash[$key])) { // key 存在
// 更新节点
/** @var DoubleListNode $node */
$node = $this->hash[$key];
$this->cache->remove($node);
$this->cache->append($node);
$node->val = $value;
} else { // key 不存在, 新增节点
$node = new DoubleListNode($key, $value);
$this->cache->append($node);
$this->hash[$key] = $node;
if ($this->size == $this->cap) {
// 删除原节点
$oldNode = $this->cache->tail->pre;
$this->cache->remove($oldNode);
unset($this->hash[$oldNode->key]);
} else {
$this->size++;
}
}
}
}
class DoubleListNode
{
public $key;
public $val;
public $pre;
public $next;
public function __construct($key = null, $val = null)
{
if ($key) $this->key = $key;
if ($val) $this->val = $val;
}
}
class DoubleList
{
public $head;
public $tail;
public function __construct()
{
$this->head = new DoubleListNode();
$this->tail = new DoubleListNode();
$this->head->next = $this->tail;
$this->tail->pre = $this->head;
}
public function append(DoubleListNode $node)
{
$node->pre = $this->head;
$node->next = $this->head->next;
$this->head->next->pre = $node;
$this->head->next = $node;
}
public function remove(DoubleListNode $node)
{
$node->pre->next = $node->next;
$node->next->pre = $node->pre;
}
}
Python Code:
来自 leetcode
class DLinkedNode:
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity: int):
self.cache = dict()
# 使用伪头部和伪尾部节点
self.head = DLinkedNode()
self.tail = DLinkedNode()
self.head.next = self.tail
self.tail.prev = self.head
self.capacity = capacity
self.size = 0
def get(self, key: int) -> int:
if key not in self.cache:
return -1
# 如果 key 存在,先通过哈希表定位,再移到头部
node = self.cache[key]
self.moveToHead(node)
return node.value
def put(self, key: int, value: int) -> None:
if key not in self.cache:
# 如果 key 不存在,创建一个新的节点
node = DLinkedNode(key, value)
# 添加进哈希表
self.cache[key] = node
# 添加至双向链表的头部
self.addToHead(node)
self.size += 1
if self.size > self.capacity:
# 如果超出容量,删除双向链表的尾部节点
removed = self.removeTail()
# 删除哈希表中对应的项
self.cache.pop(removed.key)
self.size -= 1
else:
# 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node = self.cache[key]
node.value = value
self.moveToHead(node)
def addToHead(self, node):
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def removeNode(self, node):
node.prev.next = node.next
node.next.prev = node.prev
def moveToHead(self, node):
self.removeNode(node)
self.addToHead(node)
def removeTail(self):
node = self.tail.prev
self.removeNode(node)
return node
复杂度分析
- 时间复杂度:$O(1)$
- 空间复杂度:$O(n)$ n 为容量的大小