KK's blog

每天积累多一些

0%

LeetCode 146 LRU Cache

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比较复杂,含三种情况:

  1. 已有节点
  2. 不含节点且少于容量
  3. 不含节点且大于等于容量

对应链表操作为:

  1. 删除该节点且插入到末尾(LL顺序为由旧到新
  2. 插入新节点到末尾
  3. 删除头节点且插入新节点到末尾

总结链表操作为两个:

  1. 删除某节点
  2. 插入新节点到末尾

实现上可以分为单链表和双链表。单链表要让Map指向节点的父节点。实现上很麻烦,因为更新节点都会涉及
两个keys上HashMap更新,即使已有节点换到末尾同样要两次更新Map。但双链表对此情况就避免了Map的更新。

DummyNode的选择:一开始我只选用了DummyHead,但capacity=1的时候要判断末节点是否为空很麻烦,由于
经常性的插入末节点,所以根据若头结点涉及插入删除就应该用dummyNode的原则,末节点也增加dummyNode
程序就简洁很多。

有些解法用单链表,用node映射到对应节点的父节点key_to_prev,这个方法反而不好理解,写的时候也容易错,不推荐

注意事项:

  1. set中,若节点存在,更新value。更新节点在链表中的顺序。注意三种情况,已有节点以及不含节点的两种。
  2. 头尾dummy node,初始化要相连。
  3. 注意删除顺序,先删map中的entry再删Node。否则会出现NPE。新加入是顺序相反。删除节点要将prev和next赋None
  4. 双向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) # 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; // from oldest to newest
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; // remember to update the value
}
else {
if(map.size() == capacity) {
map.remove(head.next.key);
deleteNode(head.next);
}
// add new key
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;
}
// delete head node and updated node
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)

Free mock interview