LeetCode 146 LRU Cache
Design and implement a data structure for
Least Recently Used (LRU) cache . It should support the following operations:
get
and
put
.
get(key)
- Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value)
- Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.
The cache is initialized with a
positive capacity.
Follow up: Could you do both operations in
O(1) time complexity?
Example: LRUCache cache = new LRUCache( 2 / capacity / ); cache.put(1, 1); cache.put(2, 2); cache.get(1); // returns 1 cache.put(3, 3); // evicts key 2 cache.get(2); // returns -1 (not found) cache.put(4, 4); // evicts key 1 cache.get(1); // returns -1 (not found) cache.get(3); // returns 3 cache.get(4); // returns 4
题目大意: 设计LRU。就是最就的cache会先被删除。
解题思路: 因为是Cache,get是O(1),自然想到用HashMap。如果不限容量,get,put都可以O(1)。限容量的情况下, 就要删除部分数据,这里要求按key的时间排序,所以考虑用一个串将keys串联起来。而key的添加和删除 也要O(1),所以考虑用LinkedList。HashMap和LinkedList的组合很常见。这里value就指向LL中的Node, 而Node中含key和value,key又可以让Node只向HashMap,做到互相索引。分析get和set,get就只要从Map 中读Node的value即可。set比较复杂,含三种情况:
已有节点
不含节点且少于容量
不含节点且大于等于容量
对应链表操作为:
删除该节点且插入到末尾(LL顺序为由旧到新 )
插入新节点到末尾
删除头节点且插入新节点到末尾
总结链表操作为两个:
删除某节点
插入新节点到末尾
实现上可以分为单链表和双链表。单链表要让Map指向节点的父节点。实现上很麻烦,因为更新节点都会涉及 两个keys上HashMap更新,即使已有节点换到末尾同样要两次更新Map。但双链表对此情况就避免了Map的更新。
DummyNode的选择:一开始我只选用了DummyHead,但capacity=1的时候要判断末节点是否为空很麻烦,由于 经常性的插入末节点,所以根据若头结点涉及插入删除就应该用dummyNode的原则,末节点也增加dummyNode 程序就简洁很多。
有些解法用单链表,用node映射到对应节点的父节点key_to_prev,这个方法反而不好理解,写的时候也容易错,不推荐
注意事项:
set中,若节点存在,更新value。更新节点在链表中的顺序。注意三种情况,已有节点以及不含节点的两种。
头尾dummy node,初始化要相连。
注意删除顺序,先删map中的entry再删Node。否则会出现NPE。新加入是顺序相反。删除节点要将prev和next赋None
双向LL加入新节点有两个赋值语句 ,不要忘记node.prev, node.next = predecessor, successor
Python代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class LRUCache : def __init__ (self, capacity: int) : self.key_to_node = {} self.capacity = capacity self.head = ListNode(0 , 0 ) self.tail = ListNode(0 , 0 ) self.head.next, self.tail.prev = self.tail, self.head def get (self, key: int) -> int: if key not in self.key_to_node: return -1 self.move_to_tail(self.key_to_node[key]) return self.key_to_node[key].val def put (self, key: int, value: int) -> None : if key in self.key_to_node: self.key_to_node[key].val = value self.move_to_tail(self.key_to_node[key]) else : if len(self.key_to_node) == self.capacity: self.key_to_node.pop(self.head.next.key) self.remove_node(self.head.next) node = ListNode(key, value) self.add_to_tail(node) self.key_to_node[key] = node def move_to_tail (self, node) : self.remove_node(node) self.add_to_tail(node) def add_to_tail (self, node) : predecessor, successor = self.tail.prev, self.tail predecessor.next, node.prev = node, predecessor node.next, successor.prev = successor, node def remove_node (self, node) : predecessor, successor = node.prev, node.next predecessor.next, successor.prev = successor, predecessor node.prev, node.next = None , None class ListNode : def __init__ (self, key, val, next = None, prev = None) : self.val = val self.key = key self.next = next self.prev = prev
Java代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 Map<Integer, ListNode> map; ListNode head; ListNode tail; int capacity;public L146LRUCache (int capacity) { this .capacity = capacity; head = new ListNode(-1 , -1 ); tail = new ListNode(-1 , -1 ); head.next = tail; tail.prev = head; map = new HashMap<>(); } public int get (int key) { if (!map.containsKey(key)) return -1 ; pushback(key); return map.get(key).val; } public void put (int key, int value) { if (map.containsKey(key)) { pushback(key); map.get(key).val = value; } else { if (map.size() == capacity) { map.remove(head.next.key); deleteNode(head.next); } ListNode newNode = new ListNode(key, value); addNodeToTail(newNode); map.put(key, newNode); } } void pushback (int key) { ListNode curNode = map.get(key); deleteNode(curNode); addNodeToTail(curNode); } void addNodeToTail (ListNode curNode) { ListNode prevTailNode = tail.prev; prevTailNode.next = curNode; curNode.prev = prevTailNode; curNode.next = tail; tail.prev = curNode; } void deleteNode (ListNode curNode) { ListNode nextNode = curNode.next; ListNode prevNode = curNode.prev; prevNode.next = nextNode; nextNode.prev = prevNode; curNode.next = null ; curNode.prev = null ; } public class ListNode { public int key; public int val; public ListNode next; public ListNode prev; public ListNode (int key, int val) { this .key = key; this .val = val; } }
算法分析: 时间复杂度为O(1)
,空间复杂度为O(n)
。