460:LFU缓存

问题描述

设计并实现最不经常使用(LFU)缓存的数据结构。它应该支持以下操作:get 和 put。

  • get(key) - 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1。
  • put(key, value) -如果键不存在,请设置或插入值。当缓存达到其容量时,它应该在插入新项目之前,使最不经常使用的项目无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,最近最少使用的键将被去除。

进阶:

  • 你是否可以在 O(1) 时间复杂度内执行两项操作?

思路

这题可以在结点中设置两个字段,一个是lastRead,自增型的,frequency,代表了访问频度。通过这两个字段,一次遍历我们就能拿到频度最小且lastRead最小的值。O(n)的时间就能搞定。(方法一)
参考LRU的做法,我们可以这样做:
按照频度建立一个双向链表(我们称它为横着的双向链表)。离头结点最近的是频率最小的结点。离tail结点最近的是频率最大的结点。
对于这个双向链表中的每一个结点中,我们设置另外一个双向链表,即纵向链表。这个链表中存储的是我们真正的Value值。离头结点最近的是这个频率里最之前访问的结点,离tail结点最近的是这个频率里最后访问的结点。即,同样频率的结点都被存储在同一个纵向双向链表中。
通过横向的双向链表,我们可以在O(1)的时间内找到频率最小的那个双向链表。
通过纵向链表,我们可以在O(1)的时间内找到最早访问的结点。而双向链表保证了我们在更新链表的时间花费是O(1)。(方法二)

方法一

class Value{
    int val;
    int lastRead;
    int frequecy;
    Value(int val){
        this.val = val;
    }
}
class LFUCache {
    Value[] cache;
    Map<Integer,Value> map;
    int length;
    int count;
    public LFUCache(int capacity) {
        length = capacity;
        map = new HashMap<>(capacity);
    }

    public int get(int key) {
        if(map.containsKey(key)){
            map.get(key).lastRead = count++;
            map.get(key).frequecy++;
            return map.get(key).val;
        }
        return -1;
    }

    public void put(int key, int value) {
        if(get(key) != -1) {
            map.get(key).val = value;
            return;
        }
        if(map.size() == length){
            // 达到最大容量
            int minFrequency = Integer.MAX_VALUE;
            int targetKey = -1;
            for(Integer k: map.keySet()){
                if(map.get(k).frequecy < minFrequency){
                    minFrequency = map.get(k).frequecy;
                    targetKey = k;
                }else if(map.get(k).frequecy == minFrequency){
                    if(map.get(k).lastRead < map.get(targetKey).lastRead){
                        targetKey = k;
                    }
                }
            }
            map.remove(targetKey);
        }
        if(map.size() < length){
            map.put(key,new Value(value));
            map.get(key).lastRead = count++;
        }
    }
}

方法二

import java.util.*;
public class Main{
    public static void main(String[] args){
        LFUCache lfuCache = new LFUCache(2);
        lfuCache.put(1,1);
        lfuCache.put(2,2);
        lfuCache.get(1);
    }
}
class Value{
    int key;
    int val;
    int frequency;
    Value(int key,int val){
        this.key = key;
        this.val = val;
    }
    // 供纵向双向链表用
    Value pre;
    Value next;
}
class DLHead{
    int frequency;
    // 纵向链表的头和尾  建立纵向链表
    Value head;
    Value tail;
    int length;
    DLHead(int frequency){
        this.frequency = frequency;
        head = new Value(-1,-1);
        tail = new Value(-1,-1);
        head.next = tail;
        tail.pre = head;
    }
    // 供横向链表使用
    DLHead pre;
    DLHead next;
}
class LFUCache {
    Map<Integer, Value> cache;
    Map<Integer, DLHead> freq;
    int length;
    // 建立横向链表
    DLHead head;
    DLHead tail;
    public LFUCache(int capacity) {
        cache = new HashMap<>(capacity);
        freq = new HashMap<>();
        length = capacity;
        head = new DLHead(-1);
        tail = new DLHead(-1);
        head.next = tail;
        tail.pre = head;
    }
    public int get(int key) {
        if(cache.containsKey(key)){
            // 增加频次
            updateFreq(key);
            return cache.get(key).val;
        }
        return -1;
    }
    public void put(int key, int value) {
        if(length == 0) return;
        if(cache.containsKey(key)){
            // key 已存在,则更新val,更新频次
            cache.get(key).val = value;
            updateFreq(key);
        }else{
            // key 不在,判定是否满了
            if(cache.size() == length){
                // 删除频率最低,且最久没有访问的结点
                DLHead dlHead = head.next;
                Value delValue = dlHead.head.next;
                dlHead.head.next = delValue.next;
                dlHead.head.next.pre = dlHead.head;
                cache.remove(delValue.key);
                dlHead.length--;
                if(dlHead.length == 0){
                    // 删掉这个dlHead
                    dlHead.pre.next = dlHead.next;
                    dlHead.next.pre = dlHead.pre;
                    freq.remove(dlHead.frequency);
                }
            }
            // 新插入节点
            Value insertValue = new Value(key,value);
            cache.put(key,insertValue);
            if(!freq.containsKey(insertValue.frequency)){
                // 没有这个频率的, 即需要在头后面插入
                DLHead newHead = new DLHead(insertValue.frequency);
                newHead.next = head.next;
                newHead.next.pre = newHead;
                head.next = newHead;
                newHead.pre = head;
                freq.put(insertValue.frequency,newHead);
            }
            DLHead newHead = freq.get(insertValue.frequency);
            newHead.tail.pre.next = insertValue;
            insertValue.pre = newHead.tail.pre;
            insertValue.next = newHead.tail;
            newHead.tail.pre = insertValue;
            newHead.length++;
        }
    }
    private void updateFreq(int key){
        // 找到结点
        Value tmpValue = cache.get(key);
        DLHead tmpHead = freq.get(tmpValue.frequency);
        // 更新结点的频度
        tmpValue.frequency++;
        // 将结点与竖链断链
        tmpValue.next.pre = tmpValue.pre;
        tmpValue.pre.next = tmpValue.next;
        // 更新tmpHead的长度
        tmpHead.length--;
        int newFreqVal = tmpValue.frequency;
        if(!freq.containsKey(newFreqVal)){
            // 没有这个频率,需要增加dlhead.
            DLHead newDLHead = new DLHead(tmpValue.frequency);
            newDLHead.next = tmpHead.next;
            newDLHead.pre = tmpHead;
            tmpHead.next = newDLHead;
            newDLHead.next.pre = newDLHead;
            freq.put(tmpValue.frequency,newDLHead);
        }
        if(tmpHead.length == 0){
            // 删掉tmpHead
            tmpHead.pre.next = tmpHead.next;
            tmpHead.next.pre = tmpHead.pre;
            freq.remove(tmpHead.frequency);
        }
        tmpHead = freq.get(tmpValue.frequency);
        tmpValue.pre = tmpHead.tail.pre;
        tmpValue.next = tmpHead.tail;
        tmpValue.pre.next = tmpValue;
        tmpHead.tail.pre = tmpValue;
        tmpHead.length++;
    }
}

发布了464 篇原创文章 · 获赞 21 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_41687289/article/details/105326080