Skip to content

Latest commit

 

History

History
394 lines (367 loc) · 12.4 KB

460. LFU Cache.md

File metadata and controls

394 lines (367 loc) · 12.4 KB

Difficulty : Hard

Related Topics : Design


Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the following operations: get and put.

get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1. put(key, value) - Set or insert the value if the key is not already present. When the cache reaches its capacity, it should invalidate the least frequently used item before inserting a new item. For the purpose of this problem, when there is a tie (i.e., two or more keys that have the same frequency), the least recently used key would be evicted.

Note that the number of times an item is used is the number of calls to the get and put functions for that item since it was inserted. This number is set to zero when the item is removed.

Follow up:

  • Could you do both operations in O(1) time complexity?

Example:

LFUCache cache = new LFUCache( 2 /* capacity */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // returns 1
cache.put(3, 3);    // evicts key 2
cache.get(2);       // returns -1 (not found)
cache.get(3);       // returns 3.
cache.put(4, 4);    // evicts key 1.
cache.get(1);       // returns -1 (not found)
cache.get(3);       // returns 3
cache.get(4);       // returns 4

Solution

  • mine
    • Java
      • Runtime: 90 ms, faster than 10.94%, Memory Usage: 47.7 MB, less than 7.75% of Java online submissions
        // put and get
        // O(N) time in the worst
        // O(N) time
        // N = map.size();
        class LFUCache {
            int cap;
            HashMap<Integer, Integer> map;
            HashMap<Integer, Node> cache;
        
            Node head = null;
            Node tail = null;
        
            public LFUCache(int capacity) {
                cap = capacity;
                map = new HashMap<>();
                cache = new HashMap<>();
            }
        
            public int get(int key) {
                if (!map.containsKey(key)) return -1;
                moveToHead(cache.get(key));
                return map.get(key);
            }
        
            public void put(int key, int value) {
                if (cap == 0) return;
                if (map.size() == cap && !map.containsKey(key)) {
                    deleteTail();
                }
                if (!cache.containsKey(key)) {
                    Node n = new Node(key);
                    cache.put(key, n);
                    if (tail != null) {
                        tail.next = n;
                        n.head = tail;
                        tail = n;
                    } else {
                        head = tail = n;
                    }
                }
                moveToHead(cache.get(key));
                map.put(key, value);
            }
        
            void deleteTail() {
                Node t = tail;
                map.remove(t.val);
                cache.remove(t.val);
                if(tail == head) {
                    tail = head = null;
                }else{
                    tail = tail.head;
                    tail.next = null;
                }
            }
        
            void moveToHead(Node n) {
                n.count++;
                if (head == n) return;
        
                if (n == tail && n.head.count > n.count) return;
        
                if (n == tail) {
                    tail = tail.head;
                    tail.next = null;
                }else{
                    n.head.next = n.next;
                    n.next.head = n.head;
                }
                Node t = n;
                while (n.count >= t.count && t.head != null) t = t.head;
                if (t.head == null) {
                    n.next = t;
                    n.head = null;
                    head.head = n;
                    head = n;
                } else {
                    n.next = t.next;
                    n.head = t;
                    t.next.head = n;
                    t.next = n;
                }
            }
        
            class Node {
                Node head;
                Node next;
                int val;
                int count = 0;
        
                Node(int val) {
                    this.val = val;
                }
            }
        }
        

  • the most votes
  • Runtime: 22 ms, faster than 67.25%, Memory Usage: 53.1 MB, less than 7.75% of Java online submissions
    class LFUCache {
        HashMap<Integer, Integer> vals;
        HashMap<Integer, Integer> counts;
        HashMap<Integer, LinkedHashSet<Integer>> lists;
        int cap;
        int min = -1;
        public LFUCache(int capacity) {
            cap = capacity;
            vals = new HashMap<>();
            counts = new HashMap<>();
            lists = new HashMap<>();
            lists.put(1, new LinkedHashSet<>());
        }
    
        public int get(int key) {
            if(!vals.containsKey(key))
                return -1;
            int count = counts.get(key);
            counts.put(key, count+1);
            lists.get(count).remove(key);
            if(count==min && lists.get(count).size()==0)
                min++;
            if(!lists.containsKey(count+1))
                lists.put(count+1, new LinkedHashSet<>());
            lists.get(count+1).add(key);
            return vals.get(key);
        }
    
        public void put(int key, int value) {
            if(cap<=0)
                return;
            if(vals.containsKey(key)) {
                vals.put(key, value);
                get(key);
                return;
            }
            if(vals.size() >= cap) {
                int evit = lists.get(min).iterator().next();
                lists.get(min).remove(evit);
                vals.remove(evit);
            }
            vals.put(key, value);
            counts.put(key, 1);
            min = 1;
            lists.get(1).add(key);
        }
    }
    

  • leetcode-cn Solution
  • Runtime: 29 ms, faster than 34.98%, Memory Usage: 47.4 MB, less than 7.75% of Java online submissions

    class LFUCache {
        // 缓存容量,时间戳
        int capacity, time;
        Map<Integer, Node> key_table;
        TreeSet<Node> S;
    
        public LFUCache(int capacity) {
            this.capacity = capacity;
            this.time = 0;
            key_table = new HashMap<Integer, Node>();
            S = new TreeSet<Node>();
        }
    
        public int get(int key) {
            if (capacity == 0) {
                return -1;
            }
            // 如果哈希表中没有键 key,返回 -1
            if (!key_table.containsKey(key)) {
                return -1;
            }
            // 从哈希表中得到旧的缓存
            Node cache = key_table.get(key);
            // 从平衡二叉树中删除旧的缓存
            S.remove(cache);
            // 将旧缓存更新
            cache.cnt += 1;
            cache.time = ++time;
            // 将新缓存重新放入哈希表和平衡二叉树中
            S.add(cache);
            key_table.put(key, cache);
            return cache.value;
        }
    
        public void put(int key, int value) {
            if (capacity == 0) {
                return;
            }
            if (!key_table.containsKey(key)) {
                // 如果到达缓存容量上限
                if (key_table.size() == capacity) {
                    // 从哈希表和平衡二叉树中删除最近最少使用的缓存
                    key_table.remove(S.first().key);
                    S.remove(S.first());
                }
                // 创建新的缓存
                Node cache = new Node(1, ++time, key, value);
                // 将新缓存放入哈希表和平衡二叉树中
                key_table.put(key, cache);
                S.add(cache);
            } else {
                // 这里和 get() 函数类似
                Node cache = key_table.get(key);
                S.remove(cache);
                cache.cnt += 1;
                cache.time = ++time;
                cache.value = value;
                S.add(cache);
                key_table.put(key, cache);
            }
        }
    }
    
    class Node implements Comparable<Node> {
        int cnt, time, key, value;
    
        Node(int cnt, int time, int key, int value) {
            this.cnt = cnt;
            this.time = time;
            this.key = key;
            this.value = value;
        }
    
        public boolean equals(Object anObject) {
            if (this == anObject) {
                return true;
            }
            if (anObject instanceof Node) {
                Node rhs = (Node) anObject;
                return this.cnt == rhs.cnt && this.time == rhs.time;
            }
            return false;
        }
    
        public int compareTo(Node rhs) {
            return cnt == rhs.cnt ? time - rhs.time : cnt - rhs.cnt;
        }
    
        public int hashCode() {
            return cnt * 1000000007 + time;
        }
    }
    
  • Runtime: 38 ms, faster than 25.08%, Memory Usage: 53.5 MB, less than 7.75% of Java online submissions

    class LFUCache {
        int minfreq, capacity;
        Map<Integer, Node> key_table;
        Map<Integer, LinkedList<Node>> freq_table;
    
        public LFUCache(int capacity) {
            this.minfreq = 0;
            this.capacity = capacity;
            key_table = new HashMap<Integer, Node>();;
            freq_table = new HashMap<Integer, LinkedList<Node>>();
        }
    
        public int get(int key) {
            if (capacity == 0) {
                return -1;
            }
            if (!key_table.containsKey(key)) {
                return -1;
            }
            Node node = key_table.get(key);
            int val = node.val, freq = node.freq;
            freq_table.get(freq).remove(node);
            // 如果当前链表为空,我们需要在哈希表中删除,且更新minFreq
            if (freq_table.get(freq).size() == 0) {
                freq_table.remove(freq);
                if (minfreq == freq) {
                    minfreq += 1;
                }
            }
            // 插入到 freq + 1 中
            LinkedList<Node> list = freq_table.getOrDefault(freq + 1, new LinkedList<Node>());
            list.offerFirst(new Node(key, val, freq + 1));
            freq_table.put(freq + 1, list);
            key_table.put(key, freq_table.get(freq + 1).peekFirst());
            return val;
        }
    
        public void put(int key, int value) {
            if (capacity == 0) {
                return;
            }
            if (!key_table.containsKey(key)) {
                // 缓存已满,需要进行删除操作
                if (key_table.size() == capacity) {
                    // 通过 minFreq 拿到 freq_table[minFreq] 链表的末尾节点
                    Node node = freq_table.get(minfreq).peekLast();
                    key_table.remove(node.key);
                    freq_table.get(minfreq).pollLast();
                    if (freq_table.get(minfreq).size() == 0) {
                        freq_table.remove(minfreq);
                    }
                }
                LinkedList<Node> list = freq_table.getOrDefault(1, new LinkedList<Node>());
                list.offerFirst(new Node(key, value, 1));
                freq_table.put(1, list);
                key_table.put(key, freq_table.get(1).peekFirst());
                minfreq = 1;
            } else {
                // 与 get 操作基本一致,除了需要更新缓存的值
                Node node = key_table.get(key);
                int freq = node.freq;
                freq_table.get(freq).remove(node);
                if (freq_table.get(freq).size() == 0) {
                    freq_table.remove(freq);
                    if (minfreq == freq) {
                        minfreq += 1;
                    }
                }
                LinkedList<Node> list = freq_table.getOrDefault(freq + 1, new LinkedList<Node>());
                list.offerFirst(new Node(key, value, freq + 1));
                freq_table.put(freq + 1, list);
                key_table.put(key, freq_table.get(freq + 1).peekFirst());
            }
        }
    }
    
    class Node {
        int key, val, freq;
    
        Node(int key, int val, int freq) {
            this.key = key;
            this.val = val;
            this.freq = freq;
        }
    }