題目:請你爲 最不常常使用(LFU)緩存算法設計並實現數據結構。它應該支持如下操做:get 和 put。
get(key) - 若是鍵存在於緩存中,則獲取鍵的值(老是正數),不然返回 -1。
put(key, value) - 若是鍵不存在,請設置或插入值。當緩存達到其容量時,則應該在插入新項以前,使最不常常使用的項無效。css
在此問題中,當存在平局(即兩個或更多個鍵具備相同使用頻率)時,應該去除 最近 最少使用的鍵。
「項的使用次數」就是自插入該項以來對其調用 get 和 put 函數的次數之和。使用次數會在對應項被移除後置爲 0 。node
示例:
LFUCache cache = new LFUCache( 2 /* capacity (緩存容量) */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 去除 key 2
cache.get(2); // 返回 -1 (未找到key 2)
cache.get(3); // 返回 3
cache.put(4, 4); // 去除 key 1
cache.get(1); // 返回 -1 (未找到 key 1)
cache.get(3); // 返回 3
cache.get(4); // 返回 4算法
代碼:緩存
1 class LFUCache { 2 3 public LFUCache(int capacity) { 4 5 } 6 7 public int get(int key) { 8 9 } 10 11 public void put(int key, int value) { 12 13 } 14 } 15 16 /** 17 * Your LFUCache object will be instantiated and called as such: 18 * LFUCache obj = new LFUCache(capacity); 19 * int param_1 = obj.get(key); 20 * obj.put(key,value); 21 */
LFU頁面置換算法(最不常常使用算法)數據結構
原理:ide
選擇到當前時間爲止被訪問次數最少的頁面被置換;
每頁設置訪問計數器,每當頁面被訪問時,該頁面的訪問計數器加1;
發生缺頁中斷時,淘汰計數值最小的頁面,並將全部計數清零;
函數
如圖:圖中的頁面爲三頁,依次向存儲中加入432143543215這些數字。this
而存儲空間只能存儲三個頁面,因此會按照上述規則不斷淘汰已經存儲在頁面中的數字。spa
解題思路(logN的思路):
設計
知道了LFU的置換規則後,因爲此題須要存儲的是key和value,因此
首先,須要建一個類node,存放四樣東西,key,value,times(訪問計數器),id(進入存儲空間的天然順序)
其次,選擇一種合適的數據結構來解決存儲優先級問題,此處咱們採用內部是小頂堆的PriorityQueue優先級隊列用來
實現times最小的元素在隊頭,若是times相等,則比較前後入隊的天然順序id。
可是咱們會在讓新元素入隊以前可能會刪除隊列中指定元素,固然能夠去遍歷隊列,可是這樣太慢了
咱們能夠再用一種HashMap的數據集合用來存儲節點,以便快速經過node的key來獲得整個node。
最後,即是處理邏輯關係,寫題目要求的get,put方法了
解題代碼詳解(logN):
1 public class node implements Comparable<node>{ 2 private int Key;//鍵 3 private int Value;//值 4 private int Times;//訪問計數器 5 private int Id;//天然入隊順序標記,若訪問計數器值相同,則先淘汰id小的那個 6 node() {} 7 node(int key, int value, int id) { 8 this.Key = key; 9 this.Value = value; 10 this.Id = id; 11 this.Times = 1; 12 } 13 public int getKey() { 14 return Key; 15 } 16 17 public void setKey(int Key) { 18 this.Key = Key; 19 } 20 21 public int getValue() { 22 return Value; 23 } 24 25 public void setValue(int Value) { 26 this.Value = Value; 27 } 28 29 public int getTimes() { 30 return Times; 31 } 32 33 public void setTimes(int Times) { 34 this.Times = Times; 35 } 36 public int getId() { 37 return Id; 38 } 39 40 public void setId(int id) { 41 this.Id = id; 42 } 43 44 @Override 45 public int compareTo(node o) { 46 //實現times最小的元素在隊頭,若是times相等,則比較前後入隊順序 47 int Timessub = Times - o.Times; 48 return Timessub == 0 ? this.Id - o.Id: Timessub; 49 } 50 } 51 52 class LFUCache { 53 PriorityQueue<node> KeyValueTimes = new PriorityQueue();//用於實現優先級順序 54 Map<Integer, node> nodeset;//用於O(1)取出某個具體的node 55 public int Capacity = 0;//個人cache中最大容量 56 public int nownum = 0;//cache的實時元素個數 57 public int id = 0;//每一個node的入隊天然順序標記 58 59 public LFUCache(int capacity) { 60 this.Capacity = capacity;//設置cache容量 61 nodeset = new HashMap<Integer, node>(capacity);//用於O(1)取出某個具體的node,容量依然設置爲capacity 62 } 63 64 public int get(int key) { 65 if(this.Capacity == 0)//判斷容量是否爲空,爲空則直接返回-1 66 return -1; 67 node nownode = nodeset.get(key);//經過HashMap,快速經過key鍵快速獲得node 68 if (nownode == null) {//若是key這個鍵沒在隊列中,則返回-1 69 return -1; 70 }else{ 71 KeyValueTimes.remove(nownode);//移除隊列中當前的這個node 72 nownode.setTimes(nownode.getTimes()+1);//更新當前這個node的訪問次數 73 nownode.setId(id++);//更新天然入隊順序 74 KeyValueTimes.offer(nownode);//再把它放回去 75 } 76 return nownode.getValue(); 77 } 78 79 public void put(int key, int value) { 80 if(this.Capacity == 0)//判斷容量是否爲空,爲空則不進行put 81 return; 82 node thisnode = new node(key,value,id++); 83 node oldnode = nodeset.get(key); 84 if(oldnode == null){//隊列裏不存在這個key 85 if(nownum < this.Capacity){//沒裝滿 86 KeyValueTimes.offer(thisnode);//在隊列裏添加新node 87 nodeset.put(key,thisnode);//在HashMap裏添加新node 88 nownum++;//更新當前cache的元素個數 89 } 90 else{//裝滿了,須要LRU,最近最早被移除 91 nodeset.remove(KeyValueTimes.poll().getKey());//移除隊列裏的隊頭,移除HashMap對應的那個node 92 KeyValueTimes.offer(thisnode);//在隊列裏添加新node 93 nodeset.put(key,thisnode);//在HashMap裏添加新node 94 } 95 } 96 else{//隊列裏存在這個key 97 thisnode.setTimes(oldnode.getTimes()+1);//將原來鍵爲key的訪問次數複製給新的node 98 KeyValueTimes.remove(oldnode);//移除隊列裏鍵爲key的node,移除HashMap對應的那個node 99 nodeset.remove(oldnode.getKey()); 100 KeyValueTimes.offer(thisnode);//在隊列裏添加新node,這裏新的node的value值可能會不同,因此更新了value 101 nodeset.put(key,thisnode);//在隊列裏添加新node,這裏新的node的value值可能會不同,因此更新了value 102 } 103 } 104 }