Leetcode: LRU Cache

Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.



get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.

set(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.

难度:90。这是一道非常综合的题目,主要应用在操作系统的资源管理中。

按照题目要求,要实现get和set功能,为了满足get功能:随机访问的需求(lookup)我们首先想到的一般是用数组,如果用链表会有O(n)的访问时间。然而他又有另一个条件就是要维护least used的队列,也就是说经常用的放在前面,用的少的放在后面。这样当资源超过cache的容积的时候就可以把用得最少的资源删掉。这就要求我们要对节点有好的删除和插入操作,这个要求又会让我们想到用链表,其删除插入是O(1),而数组的删除和插入是O(n)复杂度的。

lookup, insert operations in O(1) leads to use HashMap.

Hashtable也是一个不错的选择,本来它的insert, delete, lookup操作amortized代价都是O(1), 但是,这里delete无法O(1). 这是因为这里困难的是得找到合乎条件的节点去删除,需要花时间在search这个Least Recently Used节点。想过用一种方法,if we use a time stamp on each entry, deleting the one with oldest access time stamp, But it will take O(n) time cost. 所以,如果只用HashMap, 删除最老的节点需要O(n).

那么我们能不能维护一个数据结构使得访问操作和插入删除操作都是O(1)复杂度的呢?答案是肯定的。这个数据结构比较复杂,是用一个hash表加上一个双向链表来实现。

这里为什么使用Doubly linkd list 而不使用 Linked list的原因是:

当访问一个节点后,我们需要将他从原来的list中删除,然后将它插入到头节点处,删除过程中需要将其前后节点连接起来,单链表做不到。

查询:

  • 根据键值查询hashmap,若命中,则返回节点,否则返回null。
  • 从双向链表中删除命中的节点,将其重新插入到表头。
  • 所有操作的复杂度均为O(1)。

插入:

  • 将新的节点关联到Hashmap
  • 如果Cache满了,删除双向链表的尾节点,同时删除Hashmap对应的记录
  • 将新的节点插入到双向链表中头部

更新:

  • 和查询相似

删除:

  • 从双向链表和Hashmap中同时删除对应的记录。

通过hash表访问结点我们可以认为是O(1)的操作(假设hash函数足够好),然后双向链表的插入删除操作也是O(1)的操作。如此我们便实现了用O(1)时间来完成所有LRU cache的操作。空间上就是对于每一个资源有一个hash表的的项以及一个对应的结点(包含前后指针和资源的<key, value>)。代码如下:

 1 public class LRUCache {

 2     class Node {

 3         Node prev, next;

 4         int key;

 5         int value;

 6         public Node(int key, int value) {

 7             this.key = key;

 8             this.value = value;

 9         }

10     }

11     

12     int capacity;

13     int num;

14     Node first, last;

15     HashMap<Integer, Node> map;

16     

17     public LRUCache(int capacity) {

18         this.capacity = capacity;

19         this.num = 0;

20         this.first = null;

21         this.last = null;

22         this.map = new HashMap<Integer, Node>();

23     }

24     

25     public int get(int key) {

26         Node n = map.get(key);

27         if (n == null) { //no find the key

28             return -1;

29         }

30         int val = n.value;

31         if (n != last) { //find the key, and the node is not the last, need to put the node to the last of the LinkedList

32             if (n == first) { //delete the node from its original place, when the node is the first

33                 first.next.prev = null;

34                 first = first.next;

35             }

36             else { //delete the node from its original place, when the node is neither the first nor the last

37                 n.next.prev = n.prev;

38                 n.prev.next = n.next;

39             }

40             n.next = null; //put the node to the last of the Linkedlist

41             n.prev = last;

42             last.next = n;

43             last = last.next;

44         }

45         return val; //return the value of the key node

46     }

47     

48     public void set(int key, int value) {

49         Node n = map.get(key);

50         if (n != null) { //find the node, need to change its value and put to last

51             if (n != last) { //delete from its original place and put to last

52                 if (n == first) {

53                     first.next.prev = null;

54                     first = first.next;

55                 }

56                 else {

57                     n.next.prev = n.prev;

58                     n.prev.next = n.next;

59                 }

60                 n.next = null;

61                 n.prev = last;

62                 last.next = n;

63                 last = last.next;

64             }

65             n.value = value; //change the value

66         }

67         else { //not find the node, need to create one, put to the last

68             Node newNode = new Node(key, value);

69 

70             if (num >= capacity) { //no capacity, need to delete first Node

71                 map.remove(first.key);

72                 first = first.next;

73                 if (first != null) first.prev = null;

74                 else last = null;

75                 num--;

76             }

77             if (num == 0) {

78                 first = newNode;

79                 last = newNode;

80             }

81             else {

82                 newNode.next = null;

83                 newNode.prev = last;

84                 last.next = newNode;

85                 last = last.next;

86             }

87             num++;

88             map.put(key, newNode);

89         }

90     }

91 }

 关于set()函数还有另一版写法,看哪个好理解

 1     public void set(int key, int value) {

 2         if (map.containsKey(key)) { // key exist, change the value of the corresponding node

 3             CacheNode targetNode = map.get(key);

 4             if (targetNode != last) { //if target node is not the last node, put it to the last

 5                 if (targetNode != first) { //if target node is not the first node, normal operation

 6                     targetNode.prev.next = targetNode.next;

 7                     targetNode.next.prev = targetNode.prev;

 8                 }

 9                 else { // if target node is the first node, special operation

10                     targetNode.next.prev = targetNode.prev;

11                     first = targetNode.next;

12                 }

13                 targetNode.next = last.next;

14                 last.next = targetNode;

15                 targetNode.prev = last;

16                 last = last.next;

17             }

18             targetNode.val = value;

19         }

20         else { // key not exist, add the value to the LRUCache

21             CacheNode newNode = new CacheNode(key, value);

22             if (curnum < Capacity) { // LRU Capacity not reached, can add the node the LRU without deletion

23                 if (last == null) { // LRU is initially empty, newNode is the first node added

24                     last = newNode;

25                     first = newNode;

26                 }

27                 else { // LRU is not initially empty, add newNode to the last

28                     newNode.next = last.next;

29                     last.next = newNode;

30                     newNode.prev = last;

31                     last = last.next;

32                 }

33                 curnum++;

34                 map.put(key, newNode);

35             }

36             else { // curnum >= Capacity, LRU Capacity reached, deletes first node before adding newNode

37                 if (first != null) { // Capacity is not 0, it Capacity is 0, do nothing, directly go to next if()

38                     if (first == last) { // only one node in the LRU

39                         map.remove(first.key);

40                         first = null;

41                         last = null;

42                     }

43                     else { //more than one node in the LRU, delete first node

44                         first.next.prev = first.prev;

45                         map.remove(first.key);

46                         first = first.next;

47                     }

48                     curnum = Math.max(0, curnum-1);

49                 }

50                 if (curnum < Capacity) { //after deleting the firstNode, add the newNode to last.next

51                     if (last == null) { // LRU is initially empty, newNode is the first node added

52                         last = newNode;

53                         first = newNode;

54                     }

55                     else { // LRU is not initially empty, add newNode to the last

56                         newNode.next = last.next;

57                         last.next = newNode;

58                         newNode.prev = last;

59                         last = last.next;

60                     }

61                     curnum++;

62                     map.put(key, newNode);

63                 }

64             }

65         }

66     }

 

你可能感兴趣的:(LeetCode)