1、问题描述
运用你所掌握的数据结构,设计一个LRU(最近最少使用)缓存机制。它应该支持以下操作,获取数据get和写入数据put。
获取数据get(key):如果密钥存在于缓存中,则获取密钥的值(总是正数),否则返回-1;
写入数据put(key,value):如果密钥不存在,则写入其数据值,当缓存容量达到上限时,应该在写入新的数据值之前删除最近最少使用的数据值,从而从而为新的数据值腾出空间。如果密钥存在,则修改其数据值,此时该密钥对应的数据值是最近最多使用的数据值。
你是否可以在
O
(
1
)
O(1)
O(1)时间内完成这两种操作?
2、解题思路
- 分析:首先从一个示例出发
示例:
LRUCache cache = new LRUCache(2 /*缓存容量*/);
cache.put(1,1);
cache.put(2,2);
cache.get(1); //返回1,并使密钥1变为最近使用的密钥
cache.put(3,3); //该操作会使密钥2作废
cache.get(2); // 返回-1(未找到)
cache.put(1,5); // 该操作会使密钥1变为最近使用的密钥
- 从上述示例中,我们可以看出,get和put操作涉及的具体操作逻辑如下:
- get(key):如果key不在缓存中,则返回-1,如果key在缓存中,则返回key所对应的数据值,同时将key设置为最近访问的密钥;
- put(key,value):看缓存是否达到了容量上限,若达到,则将最近最少使用的密钥对应的数据值删除,写入新的数据值value;若没有达到,则直接写入新的数据值。同时将key设置为最近访问的密钥。
- 思路1:从上述分析来看,无论是get(key)还是put(key,value)操作,都需要将(key,value)设置为最新访问的内容,所以,我们可以使用链表来存储(key,value)对,即每一个(key,value)对作为链表中的一个结点。
- 当执行get(key)操作时,将链表中密钥值为key的结点移动到链表的末尾。
- 当执行put(key,value)操作时,看链表中是否存在密钥为key的结点,若存在删除该结点;若不存在,再看链表长度是否达到缓存容量,如果达到,则删除头节点。最后将(key,value)作为新的尾节点插入到链表。
- 从上面可以看出,当使用链表作为cache(缓存)的时候,在该数据结构上涉及的主要操作是:
- (1)将链表中的某个key节点移动到末尾;
- (2)删除链表的头节点;
- (3)在链表的末尾插入新的节点;
- 接下来,我们看看每种操作是的时间复杂度:
- 操作(1)由于需要在链表中查找密钥值为key的结点,所以时间复杂度为 O ( n ) O(n) O(n),而操作(2)、(3)是链表的插入删除操作,所以时间复杂度为 O ( 1 ) O(1) O(1)
- 如果要想实现题目要求,那么cache这个数据结构必须具有查找快、插入快、删除快、有顺序之分的特点。
- 我们知道,哈希表查找快,但是数据无顺序之分;而链表虽然插入快、删除快、并且有顺序之分,但是查找慢,所以结合以下,形成新的数据结构——哈希链表。
- LRU算法的核心数据结构就是哈希链表,双向链表和哈希表的结合体,具体如下:
- 思想很简单,就是借助哈希表赋予链表快速查找的特性:可以快速查找某个key是否在链表中,同时可以快速插入和删除结点。
- 但可能会有一些问题存在,比如:
- (1)为什么使用双向链表,单链表不行吗?
- 主要是因为在链表中插入和删除某个结点的时候,我们需要知道该链表的前一个结点,如果使用单链表,无法在 O ( 1 ) O(1) O(1)时间内完成。
- (2)既然在哈希表中存储了key值,为什么还要在链表中存储key值;
- 因为我们删除链表中某个结点的时候,同时还要把哈希表中映射到该结点的key值删除,如果链表中的结点只存储了value值,我们就不知道key值是什么,从而无法删除哈希表中的键。
3、代码实现
class LRUCache:
def __init__(self, capacity: int):
self.HashDLink = {}
self.capacity = capacity
head_key = 'head'
HeadNode = DLinkNode(head_key,None)
tail_key = 'tail'
TailNode = DLinkNode(tail_key,None)
HeadNode.nex = TailNode
TailNode.pre = HeadNode
self.HashDLink[head_key] = HeadNode
self.HashDLink[tail_key] = TailNode
def get(self, key: int) -> int:
if key not in self.HashDLink.keys():
return -1
curnode = self.HashDLink[key]
curnode.pre.nex = curnode.nex
curnode.nex.pre = curnode.pre
tailnode = self.HashDLink['tail']
tailnode.pre.nex = curnode
curnode.pre = tailnode.pre
curnode.nex = tailnode
tailnode.pre = curnode
return self.HashDLink[key].val
def put(self, key: int, value: int) -> None:
if self.capacity > 0:
delnode = None
if key in self.HashDLink.keys():
delnode = self.HashDLink[key]
else:
if len(self.HashDLink) >= self.capacity + 2:
delnode = self.HashDLink['head'].nex
if delnode != None:
delnode.pre.nex = delnode.nex
delnode.nex.pre = delnode.pre
del self.HashDLink[delnode.key]
curnode = DLinkNode(key,value)
tailnode = self.HashDLink['tail']
tailnode.pre.nex = curnode
curnode.pre = tailnode.pre
curnode.nex = tailnode
tailnode.pre = curnode
self.HashDLink[key] = curnode
class DLinkNode:
def __init__(self,key,val):
self.key = key
self.val = val
self.pre = None
self.nex = None