LeetCode 146 LRUCache Python题解
来源:互联网 发布:服装店记账软件 编辑:程序博客网 时间:2024/06/07 19:26
LRUCache全名为Least Recently Used,即最近最少使用算法,是操作系统中发生缺页中断时常用的一种页面置换算法。
根据局部性原理,最近使用的数据块很有可能继续被频繁使用,因此当Cache已满的时候,LRUCache算法会把最久未使用的数据块替换出去。
对于LRU算法主要实现两个操作:
- 访问数据块
- 将访问的数据块更新为最近访问,并返回访问的数据块。
- 添加数据块
- 如果Cache还有容量,将添加的数据块添加到Cache之后标记为最近访问。
- 如果Cache的容量已满,替换最久未访问的数据块为添加的数据块。
关于数据块的最近访问顺序可以表示为一个list,list中的元素按照访问顺序从最久到最近排序,当需要替换数据块的时候弹出list的首个元素,并将添加的数据块放到队尾;当需要访问数据块的时候,将访问的数据块放到队尾。
第一版代码
class LRUCache(object): def __init__(self, capacity): self.capacity = capacity self._cache = [] self._cache_look_up = {} def get(self, key): if key not in self._cache_look_up: return -1 self._cache.remove(key) self._cache.append(key) return self._cache_look_up[key] def put(self, key, value): if key in self._cache_look_up: self._cache_look_up[key] = value self._cache.remove(key) self._cache.append(key) return else: if len(self._cache) == self.capacity: del_key = self._cache[0] self._cache = self._cache[1:] del self._cache_look_up[del_key] self._cache.append(key) self._cache_look_up[key] = value
这里使用了Python中内置的list数据结构作为保存访问顺序的队列,Python list实现为一块连续分配的内存(即C中的数组),因此如果list中删除元素或者插入元素的时间复杂度都为
除了保存访问顺序的队列以外,还需要保存key和value之间的对应关系,在这里直接使用Python dict来实现,相比C++中使用红黑树来实现的map,Python dict是通过hash table来实现的,因此搜索元素的时间复杂度能达到
Get(Θ(n) )
首先判断key是否在Cache之中存在,一种很常见的写法是:
if key not in self.cache_look_up.keys(): return -1
dict的keys方法返回了一个包含所有key的list,因此in操作的时间复杂度就变为
if key not self.cache_look_up: return -1
参考链接:Check if a given key already exists in a dictionary(其中第二个回答)
接下来就是将最近访问的数据块放到队尾,这里使用了list数据结构,所以时间复杂度为
Put(Θ(n) )
在设置新的数据块的时候,主要的耗时操作还是在list中删除或移动元素,时间复杂度也是为
Result(TLE 17/18)
结果妥妥地TLE,毕竟题目要求的是两个操作都必须实现为
第二版代码
优先队列除了使用传统的list来实现,还可以使用heap来实现,在heap中操作的时间复杂度一般为
在list中,直接可以通过key的位置的顺序表示访问顺序,但在堆中做不到,因此需要在堆的节点中存储一个访问时间,由于heapq库中没有提供针对节点接口的比较,因此节点自身需要重载比较运算符:
import timeclass HeapNode(object): def __init__(self, key, value): self.key = key self.value = value self.access_time = time.time() def update_time(self): self.access_time = time.time() def __lt__(self, other): return self.access_time < other.access_time def __ge__(self, other): return self.access_time >= other.access_time def __le__(self, other): return self.access_time <= other.access_time def __cmp__(self, other): return self.access_time == other.access_time
实现堆的一些常用ADT:
class Heap(object): def __init__(self): self.heap = [] self.heap_size = 0 def insert(self, node): self.heap_size += 1 heapq.heappush(self.heap, node) def heapify(self): self.heap.sort() def pushpop(self, node): return heapq.heappushpop(self.heap, node)
最后实现LRUCache:
class LRUCache(object): def __init__(self, capacity): self.capacity = capacity self.cache = Heap() self.cache_look_up = {} def get(self, key): if key not in self.cache_look_up: return -1 heap_node = self.cache_look_up[key] heap_node.update_time() self.cache.heapify() return heap_node.value def put(self, key, value): if key in self.cache_look_up: heap_node = self.cache_look_up[key] heap_node.value = value heap_node.update_time() self.cache.heapify() else: new_node = HeapNode(key, value) self.cache_look_up[key] = new_node if self.cache.heap_size == self.capacity: min_node = self.cache.pushpop(new_node) self.cache_look_up.pop(min_node.key) return self.cache.insert(new_node)
Get(Θ(log2N) )
获取key的时候直接查询dict即可,但是还需要在堆的优先队列中更新key对应的节点访问时间,在堆中维护一次堆性质的时间复杂度为
Put(Θ(log2N) )
在堆中弹出最小值,并将最小值替换成新插入的节点,维护一次堆性质,时间复杂度为
Result(TLE 16/18)
虽然堆实现的优先队列的时间复杂度比线性时间更少,但是在通过leetcode测试的时候明显比第一版代码更慢,因此有可能是堆的常数操作花费的时间更多。
第三版代码
在Python list中,即一段连续分配的内存中,移动和删除元素需要的时间复杂度都为
但是在链表中搜寻元素需要遍历链表,即
从哈希表中获取相对应的节点之后,还要对节点之间的移动,单向链表不能很好的满足需求,因此需要实现为双向链表。
class LinkNode(object): def __init__(self, key, val, prev_node=None, next_node=None): self.key = key self.val = val self.prev = prev_node self.next = next_nodeclass DoubleLinkList(object): def __init__(self, capacity): self.capacity = capacity self.size = 0 self.head = None self.tail = None def append(self, node): """ :param node: :return: append a node to the double link list last """ if self.size == self.capacity: raise ValueError("The double link list has been full.") self.size += 1 if self.head is None: self.head = self.tail = node return self.tail.next = node node.prev = self.tail self.tail = node def delete(self, node): """ :param node: :return node: delete a node in double link list. switch(node): 1.node == self.head 2.node == self.tail 3.node in the double link list middle """ if self.size == 0: raise ValueError("can not delete empty double link list") self.size -= 1 if node == self.head: if node.next: node.next.prev = None self.head = node.next elif node == self.tail: if node.prev: node.prev.next = None self.tail = node.prev else: node.prev.next = node.next node.next.prev = node.prev return nodeclass LRUCache(object): def __init__(self, capacity): self.capacity = capacity self.cache_look_up = {} self.cache_list = DoubleLinkList(capacity) def get(self, key): if key not in self.cache_look_up: return -1 node = self.cache_look_up[key] self.cache_list.delete(node) self.cache_list.append(node) return node.val def put(self, key, value): if key in self.cache_look_up: node = self.cache_look_up[key] node.val = value self.cache_list.delete(node) self.cache_list.append(node) else: if self.capacity == self.cache_list.size: head_node = self.cache_list.delete(self.cache_list.head) del self.cache_look_up[head_node.key] new_node = LinkNode(key, value) self.cache_look_up[key] = new_node self.cache_list.append(new_node)
Get(Θ(1) )
在Python dict中搜寻元素的时间复杂度为
Put(Θ(1) )
与Get的分析基本相同,操作基本可以在
Result(Accpeted)
第四版代码
也可以使用Collections中的OrderedDict来实现:
from collections import OrderedDictclass LRUCache(object): def __init__(self, capacity): self.capacity = capacity self.cache = OrderedDict() def get(self, key): if key not in self.cache: return -1 value = self.cache[key] self.cache.pop(key) self.cache[key] = value return value def put(self, key, value): if key in self.cache: self.cache.pop(key) self.cache[key] = value else: if len(self.cache.keys()) == self.capacity: self.cache.popitem(last=False) self.cache[key] = value
Result
如果还有更好的方法,欢迎跟我一起交流。
- LeetCode 146 LRUCache Python题解
- [LeetCode]LRUcache题解(不使用STL容器)
- leetcode 部分题解(python)
- leetcode LRUCache
- Leetcode- LRUCache
- LRUCache---leetcode
- 【LeetCode】【Python题解】Same Tree
- 【LeetCode】【Python题解】Reverse Integer
- 【LeetCode】【Python题解】Single NumberII
- 【LeetCode】【Python题解】Rotate Image
- leetcode Add Binary python 题解
- LeetCode 135:candy 题解 Python
- [LeetCode] Reverse String Python 题解
- [LeetCode] Two Sum Python 题解
- 【LeetCode】【Python题解】Unique Binary Search Trees
- 【LeetCode】【Python题解】Pascal's Triangle
- 【LeetCode】【Python题解】Container with most water
- leetcode Valid Palindrome C++&python 题解
- Linux系统:ls命令(学习记录)
- SSL/TLS的Handshake过程与javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure异常
- 三分相关
- 在Java中直接调用js代码
- C++之多态性与虚函数
- LeetCode 146 LRUCache Python题解
- win7 64位+vs2010+opencv2.4.10+cmake3.6编译报错的解决
- LinkedList模拟栈和队列
- DLUT C++上机作业(实验七)
- 构造函数与普通函数的区别
- Sublime Text3--插件安装
- 【笔记】An ffmpeg and SDL Tutorial 01《视频帧保存到PPM文件》
- 菜鸟说Less
- java学习(7)