【Lintcode】24. LFU Cache
技術標籤:# 棧、佇列、串及其他資料結構連結串列javahashmap
題目地址:
https://www.lintcode.com/problem/lfu-cache/description
要求實現一個LFU(Least Frequently Used)快取。它是個類似key-value的資料庫,有個容量
c
c
c。要求實現下面操作:
1、int get(int key)
,獲得key對應的value,如果該key不存在則返回
−
1
-1
−1;
2、void set(int key, int value)
,如果該key不存在,則將此鍵值對加入資料庫;否則修改該鍵值對的值為value。如果加入之前整個資料庫的size已經達到了容量
c
c
思路是連結串列套連結串列。我們不妨將外層連結串列的節點稱為一個塊(block),這個block裡存訪問次數相同的連結串列節點,也存一下這個訪問次數,而這些連結串列節點按照LRU的順序排列。block之間也形成一個連結串列,按照訪問次數升高來排列。另外開一個雜湊表,key存資料庫裡鍵值對的key,value存連結串列節點,而連結串列節點裡也要存一下key和value兩個資訊,此外,連結串列節點還要存一下它處於哪個block中。兩個操作做法如下:
1、對於get:先通過雜湊表找到那個連結串列節點(如果找不到那就說明不存在該key,直接返回
−
1
-1
2、對於set:如果雜湊表裡存在該key,則改掉其對應的value,然後再get這個key即可;如果不存在,那就要插入這個key。我們先要看一下整個cache的size是否達到了最大容量,如果是,則找到最小訪問次數對應的block,將這個block裡的連結串列尾結點刪掉(這裡一定要把雜湊表裡的這個tail對應的key也刪掉,而這個key恰好在連結串列節點裡也有,這就是為什麼連結串列節點裡一定要存key的原因),如果該block裡連結串列空了,則要將這個block也刪掉;然後,再找到或者new出訪問次數是
1
1
程式碼如下:
import java.util.HashMap;
import java.util.Map;
public class LFUCache {
class ListNode {
int key, val;
// 記錄這個node在哪個block裡
Block block;
ListNode prev, next;
public ListNode(int key, int val) {
this.key = key;
this.val = val;
}
}
class Block {
// 訪問次數
int freq;
Block prev, next;
ListNode dummy, tail;
public Block(int freq) {
this.freq = freq;
dummy = tail = new ListNode(0, 0);
}
// 在當前block裡的連結串列頭插入node,要注意一下tail被更新的情況,並且要維護node的block指標
public void insertFirst(ListNode node) {
node.next = dummy.next;
node.prev = dummy;
node.prev.next = node;
if (node.next != null) {
node.next.prev = node;
}
// 噹噹前連結串列裡沒有節點的時候要更新tail
if (tail == dummy) {
tail = node;
}
node.block = this;
}
public void remove(ListNode node) {
if (node == tail) {
tail = tail.prev;
}
node.prev.next = node.next;
if (node.next != null) {
node.next.prev = node.prev;
}
}
}
private Map<Integer, ListNode> map;
private Block blockDummy;
private int capacity, size;
/*
* @param capacity: An integer
*/
public LFUCache(int capacity) {
// do intialization if necessary
this.capacity = capacity;
map = new HashMap<>();
blockDummy = new Block(0);
}
/*
* @param key: An integer
* @param value: An integer
* @return: nothing
*/
public void set(int key, int value) {
// write your code here
// 如果含該key,則更新其value,再get一下,就能將其freq增加1了
if (map.containsKey(key)) {
map.get(key).val = value;
get(key);
return;
}
// 不含這個key,說明要插入
// 如果size達到了上限,則先要清理freq最小的block裡的tail節點
if (size == capacity) {
Block minFreqblock = blockDummy.next;
// 這裡是為了防止capacity是0的情況
if (minFreqblock == null) {
return;
}
// 刪掉這個block裡的連結串列的tail
map.remove(minFreqblock.tail.key);
minFreqblock.remove(minFreqblock.tail);
// 如果該block沒有連結串列節點了,則將該block刪除
if (minFreqblock.tail == minFreqblock.dummy) {
minFreqblock.prev.next = minFreqblock.next;
if (minFreqblock.next != null) {
minFreqblock.next.prev = minFreqblock.prev;
}
}
size--;
}
// new出連結串列節點,並存入雜湊表
ListNode node = new ListNode(key, value);
map.put(key, node);
Block oneFreqBlock = null;
// 如果沒有freq是1的block,則new出來,然後將node加進去
if (blockDummy.next == null || blockDummy.next.freq != 1) {
// new出freq是1的block
oneFreqBlock = new Block(1);
// 將這個freq是1的block插在blockDummy的後面
oneFreqBlock.next = blockDummy.next;
oneFreqBlock.prev = blockDummy;
oneFreqBlock.prev.next = oneFreqBlock;
if (oneFreqBlock.next != null) {
oneFreqBlock.next.prev = oneFreqBlock;
}
} else {
// 走到這裡說明存在freq是1的block
oneFreqBlock = blockDummy.next;
}
oneFreqBlock.insertFirst(node);
size++;
}
/*
* @param key: An integer
* @return: An integer
*/
public int get(int key) {
// write your code here
if (!map.containsKey(key)) {
return -1;
}
ListNode node = map.get(key);
// 取出node所在的block
Block block = node.block;
// 取出freq並看一下是否存在freq + 1的block
int freq = block.freq;
Block plus1Block = null;
// 如果不存在freq + 1的block,則new出來
if (block.next == null || block.next.freq != freq + 1) {
plus1Block = new Block(freq + 1);
plus1Block.next = block.next;
plus1Block.prev = block;
plus1Block.prev.next = plus1Block;
if (plus1Block.next != null) {
plus1Block.next.prev = plus1Block;
}
} else {
plus1Block = block.next;
}
// 接著將node從其所在的block裡分離出來
// 如果這個node是這個block裡唯一的節點,那就將這個block刪掉
if (block.tail.prev == block.dummy) {
block.prev.next = block.next;
block.next.prev = block.prev;
} else {
// 否則的話就將該node從這個block裡刪掉
block.remove(node);
}
// 刪完之後,要將node加入plus1Block裡
plus1Block.insertFirst(node);
node.block = plus1Block;
return node.val;
}
}
所有操作時間複雜度 O ( 1 ) O(1) O(1),空間 O ( c ) O(c) O(c)。