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的主要内容,如果未能解决你的问题,请参考以下文章