LRU Cache

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LRU Cache相关的知识,希望对你有一定的参考价值。

Question: 

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.

 

Analysis:

LRU(Least recently used)翻译成中文就是最近最不常用的。LRU cache需要做的事情就是:1. 能够根据key获取相对应的value。2. 能够往里面添加(key,value)值。我们知道cache的大小是固定的,不能往里面无限添加。所以当放满的时候,需要一种淘汰机制来删去一些值,好让新的值可以放入。这里就用到所谓的LRU。
 
假设cache的capacity是3,然后往里面set值((1, 1),(2, 2),(3, 3)),这时cache变成[(1, 1),(2, 2),(3, 3)]。当set((4, 4)),就要删掉那个最不常用的值,这里就是第一个元素(1, 1)(因为它是最早进入cache的,并且往后再也没用过),cache变成[(2, 2),(3, 3),(4, 4)]。然后get(2),我们把2移到末端,变成[(3, 3),(4, 4),(2, 2)]。然后set(3, 5),这时先把(3, 3)变成(3, 5),然后将它移到cache的末尾,最后就是[(4, 4),(2, 2),(3, 5)]。
 
总结一下,发现cache需要支持查找操作,自然想到查找最快的是HashMap。又要支持将中间的元素移到末尾这一操作,所以想到LinkedList。所以这道题要种这两种数据结构来支持。还有一个问题就是HashMap里key和value分别放什么,如果就放<key, value>,那么当我们需要将一个已经存在的key移到list末尾的时候,需要从头到尾扫一遍list,检查当前node的key是否和我们要找的key相等,这显然不是一个高效的做法。我们可以在map里存<key, prev node>,这样就能通过key直接定位到相应的node进行操作(存prev node是因为,在list中删除一个节点我们需要知道它的前一个节点)。
 
Code:
 1 public class Solution {
 2     class ListNode {
 3         int key, val;
 4         ListNode next;
 5         
 6         ListNode(int key, int val) {
 7             this.key = key;
 8             this.val = val;
 9             this.next = null;
10         }
11     }
12     
13     
14     int capacity;
15     HashMap<Integer, ListNode> map;
16     ListNode dummy, tail;
17     
18     // @param capacity, an integer
19     public Solution(int capacity) {
20         this.capacity = capacity;
21         this.map = new HashMap<Integer, ListNode>();
22         dummy = new ListNode(Integer.MIN_VALUE, -1);
23         tail = dummy;
24     }
25 
26     // @return an integer
27     public int get(int key) {
28         if(map.containsKey(key)) {
29             ListNode prev = map.get(key);
30             moveToTail(prev);
31             
32             return map.get(key).next.val;
33         }else {
34             return -1;
35         }
36     }
37 
38     // @param key, an integer
39     // @param value, an integer
40     // @return nothing
41     public void set(int key, int value) {
42         if(get(key) != -1) {
43             tail.val = value;
44         }else {
45             if(map.size() == capacity) {
46                 ListNode removeNode = dummy.next;
47                 dummy.next = dummy.next.next;
48                 map.remove(removeNode.key);
49                 //一定要检查dummy.next,如果capacity是1,不检查的话会出错
50                 if(dummy.next != null) {
51                     map.put(dummy.next.key, dummy);
52                 }
53             }
54             
55             ListNode node = new ListNode(key, value);
56             tail.next = node;
57             map.put(key, tail);
58             
59             tail = node;
60         }
61     }
62     
63     private void moveToTail(ListNode prev) {
64         //if key is at the end of list, do nothing
65         if(prev.next == tail) {
66             return;
67         }
68         
69         ListNode temp = prev.next;
70         prev.next = prev.next.next;
71         //这里可以不检查
72         if(prev.next != null) {
73             map.put(prev.next.key, prev);    //whenever the structure of list changes, we need to update map
74         }
75         
76         tail.next = temp;
77         temp.next = null;
78         map.put(temp.key, tail);    //whenever the structure of list changes, we need to update map
79         
80         tail = temp;
81     }
82 }

 

Complexity:
get和set的时间复杂度都是O(1)。
 

以上是关于LRU Cache的主要内容,如果未能解决你的问题,请参考以下文章

LRU算法

LRU

LeetCode:LRU Cache

146. LRU Cache

缓存淘汰算法--LRU算法

缓存淘汰算法--LRU算法