LFU缓存结构设计(最近最少使用)

前言:

因为面试要求当场撸LFU缓存结构设计题面试的时候没有写出来,所以这里做一个缓存算法集合。😭
1.FIFO(先进先出队列)
2.LRU(最近最久未使用)
3.LFU(最近最少使用)

题目:

leetcode_LFU
牛客_LFU
请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。它应该支持以下操作:get 和 put。

get(key) - 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1。
put(key, value) - 如果键已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量时,则应该在插入新项之前,使最不经常使用的项无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除最久未使用的键。
「项的使用次数」就是自插入该项以来对其调用 get 和 put 函数的次数之和。使用次数会在对应项被移除后置为 0 。

要求:

在 O(1) 时间复杂度内执行两项操作

示例:

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

思路:

首先就是要明白什么叫LFU(最近最少使用),和LRU类似,但不同的在于缓存满了删除的时候,LFU是删除 使用次数(频率)最少的元素。tip:不懂区别的的建议先看LRU的那题
1.同样的,根据题目要求O(1)的解法,所以我们需要一个Hash表来进行元素的定位(缓存元素)。Map<Integer,Node> cache
2.然后我们采用多个双向链表,来存储每个频率有那些元素Map<Integer,DLinked> num_map,将其链接起来,链表head的就是当前频率最久未使用的,tail的就是当前频率最近使用的
3.我们需要一个min标记,用来标记当前最小的频率是几

删除操作过程:通过min标记得到最小频率, 通过num_map.get(min)得到频率对应的双向链表DLinked,DLinked.head.next就是对应最近最少使用的Node, 然后进行相关删除操作即可

代码:

class LFUCache {
    private Map<Integer,Node> cache;
    private Map<Integer,DLinked> num_map;
    private Integer size;
    private Integer min=0;
    class Node{
        int key;
        int value;
        //频率
        int num;
        Node next;
        Node pre;
        public Node(int key,int value){
            this.key=key;
            this.value=value;
            num=1;
        }
    }
    class DLinked{
        Node head;
        Node tail;
        public DLinked(){
            head=new Node(0,0);
            tail=new Node(0,0);
            head.next=tail;
            tail.pre=head;
        }
        //从该链表中移除
        public void remove(Node node){
            node.pre.next=node.next;
            node.next.pre=node.pre;
        }
        //加到链表尾部.pre
        public void AddToTail(Node node){
            node.next=tail;
            node.pre=tail.pre;
            tail.pre.next=node;
            tail.pre=node;
        }
    }
    //初始化操作
    public LFUCache(int capacity) {
        size=capacity;
        min=0;
        cache = new HashMap<>();
        num_map=new HashMap<>();
    }
    
    public int get(int key) {
        Node node = cache.get(key);
        //如果缓存中包含该元素
        if(node!=null){
            //更新该元素的位置即可
            num_update(node);
            return node.value;
        }
        return -1;
    }
    
    public void put(int key, int value) {
        //特判,如果缓存大小为0的话,那么直接return即可
        if(size==0){
            return ;
        }
        Node node = cache.get(key);
        //如果该元素存在,则更新元素位置即可(频率)
        if(node!=null){
            node.value=value;
            num_update(node);
        }else{
            //如果元素不存在

            //如果缓存满了
            if(cache.size()==size){
                //需要把最久未使用且频率最低的给删掉

                DLinked min_linked=num_map.get(min);
                cache.remove(min_linked.head.next.key);
                min_linked.remove(min_linked.head.next);
            }

            Node newNode = new Node(key,value);
            cache.put(key,newNode);
            DLinked dlinked = num_map.get(1);
            if(dlinked==null){
                dlinked = new DLinked();
                num_map.put(1,dlinked);
            }
            dlinked.AddToTail(newNode);
            min=1;
        }
    }

    //更新元素的位置
    private void num_update(Node node){
        //获取该元素(频率)所在的链表
        DLinked dlinked= num_map.get(node.num);
        //将其从该链表中移除
        dlinked.remove(node);
        //如果当前更新的元素的num(频率)为最小频率,且移除该元素后,此链表中没有元素了,则更新min
        if(node.num==min&&dlinked.head.next==dlinked.tail){
            min=node.num+1;
        }

        //需要将其更新到下一个频率的链表中
        node.num++;
        DLinked nextlinked= num_map.get(node.num);
        if(nextlinked==null){
            nextlinked = new DLinked();
            num_map.put(node.num,nextlinked);
        }
        nextlinked.AddToTail(node);
    }
}
posted @ 2020-08-17 01:55  jealous-boy  阅读(512)  评论(0编辑  收藏  举报