1. 程式人生 > 其它 >【Lintcode】24. LFU Cache

【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

c,則要刪除使用次數最少的那個key對應的鍵值對,如果有多個使用次數最少的鍵值對,則刪除那個最近被訪問的時間最早的那個鍵值對。

思路是連結串列套連結串列。我們不妨將外層連結串列的節點稱為一個塊(block),這個block裡存訪問次數相同的連結串列節點,也存一下這個訪問次數,而這些連結串列節點按照LRU的順序排列。block之間也形成一個連結串列,按照訪問次數升高來排列。另外開一個雜湊表,key存資料庫裡鍵值對的key,value存連結串列節點,而連結串列節點裡也要存一下key和value兩個資訊,此外,連結串列節點還要存一下它處於哪個block中。兩個操作做法如下:
1、對於get:先通過雜湊表找到那個連結串列節點(如果找不到那就說明不存在該key,直接返回 − 1 -1

1),然後通過連結串列節點找到它所在的block,這樣就知道了get的key被訪問了多少次,接著new出次數加 1 1 1的block(如果已經存在就不用new了),然後將這個key對應的連結串列節點從它所在的block裡刪掉,並插入到次數加 1 1 1的那個block裡的連結串列開頭(對應著這個key是最近訪問的);
2、對於set:如果雜湊表裡存在該key,則改掉其對應的value,然後再get這個key即可;如果不存在,那就要插入這個key。我們先要看一下整個cache的size是否達到了最大容量,如果是,則找到最小訪問次數對應的block,將這個block裡的連結串列尾結點刪掉(這裡一定要把雜湊表裡的這個tail對應的key也刪掉,而這個key恰好在連結串列節點裡也有,這就是為什麼連結串列節點裡一定要存key的原因),如果該block裡連結串列空了,則要將這個block也刪掉;然後,再找到或者new出訪問次數是 1 1
1
的那個block,new出key對應的連結串列節點,將其加入這個block即可。

程式碼如下:

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)