BM101 设计LFU缓存结构

1.题目描述

一个缓存结构需要实现如下功能。

  • set(key, value):将记录(key, value)插入该结构
  • get(key):返回key对应的value值

但是缓存结构中最多放K条记录,如果新的第K+1条记录要加入,就需要根据策略删掉一条记录,然后才能把新记录加入。这个策略为:在缓存结构的K条记录中,哪一个key从进入缓存结构的时刻开始,被调用set或者get的次数最少,就删掉这个key的记录;

如果调用次数最少的key有多个,上次调用发生最早的key被删除

这就是LFU缓存替换算法。实现这个结构,K作为参数给出

数据范围:0<k≤1050<k≤105,∣val∣≤2×109∣val∣≤2×109

要求:get和set的时间复杂度都是 O(logn)O(logn),空间复杂度是 O(n)O(n)

若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1
 

对于每个操作2,返回一个答案

示例1

输入:

[[1,1,1],[1,2,2],[1,3,2],[1,2,4],[1,3,5],[2,2],[1,4,4],[2,1]],3

返回值:

[4,-1]

说明:

在执行"1 4 4"后,"1 1 1"被删除。因此第二次询问的答案为-1   

备注:

1≤k≤n≤1051≤k≤n≤105
−2×109≤x,y≤2×109−2×109≤x,y≤2×109

2.解题思路

本题是BM100设计LRU缓存结构的升级版,LFU不仅要判断哪个结点是最久没被使用,还需要多加一个访问频率freq,如果当前页面中已经有k个元素,再put新元素进来时,就需要按照置换规则选择出一个访问频率最低,且最久没被使用过的结点删除,然后再将新元素加入。

因此在设计数据结构时,需要使用一个nodeMap存储key-node对,表示此时已经put进来的结点个数,可以在O(1)的时间获取当前key是否存在;

还需要一个freqMap存储freq-DeLinkedList对,其中freq为每个结点的访问频率,DeLinkedList为一个双向链表,用于记录访问频率为freq的所有结点,最近访问过的结点放在表尾,最久没被访问过的放在表头,还需要一个minFreq记录当前最小的访问频率是多少,便于在nodeMap大小为k时,以O(1)的复杂度快速删除掉需要被置换出去的结点;

本题的注意点有以下几个:

1.在向freqMap中加入当问频率为freq的结点时,如果当前freqMap中不存在key为当前freq的结点,需要先put进一个初始化为两个哨兵结点的双向链表,然后再加入;

2.先弹出访问频率为freq的结点,每次弹出都要判断是否 freq == minFreq && freqMap[freq].size == 0,如果是,要更新minFreq,令其+=1;然后freq+=1,再把这个结点加入到freq+1的双向链表中;

3.如果put的结点key已经存在,那么nodeMap只需要更新val,freq,无需弹出,只有nodeMap.size == k时,才需要从nodeMap中弹出结点,因为nodeMap只是记录某一个结点是否存在,大部分的delete,add都是在双向链表中进行的

4.需要在双向链表中删除和插入结点的几种情况:(1).get(key)已存在,先删除再向频率+1的链表插入;(2).put(key,val)已存在,先删除源频率双向链表中的结点,再向频率+1的链表插入;(3).put(key,val)不存在,看nodeMap().size==k吗,如果是先置换出对应结点,再新建一个节点,put进去;否则直接新建一个节点put进去即可。

3.代码实现

import java.util.*;

class LNode {
    public int key;
    public int val;
    public int freq;

    public LNode prev;
    public LNode next;
    public LNode(int key, int val) {
        this.key = key;
        this.val = val;
        this.freq = 1;
        this.prev = null;
        this.next = null;
    }
}

/**
 * 频率-双向链表 freqMap中对应的双向链表结构
 */
class DeLinkedList {
    int size;
    LNode head = null;
    LNode tail = null;

    public DeLinkedList() {
        size = 0;
        head = new LNode(-1, -1);
        tail = new LNode(-1, -1);
        head.next = tail;
        tail.prev = head;
    }

    public void add(LNode node) {
        LNode pre = tail.prev;
        pre.next = node;
        node.prev = pre;
        node.next = tail;
        tail.prev = node;
        size += 1;
    }

    public LNode delete (LNode node) {
        LNode pre = node.prev;
        LNode next = node.next;
        next.prev = pre;
        pre.next = next;
        size -= 1;
        return node;
    }
}


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * lfu design
     * @param operators int整型二维数组 ops
     * @param k int整型 the k
     * @return int整型一维数组
     */
    public Map<Integer, LNode> nodeMap = new HashMap<>();

    //存放出现频率 和 这个频率中所有的元素构成的双向链表
    public Map<Integer, DeLinkedList> freqMap = new HashMap<>();

    public int minFreq = 1;


    public int[] LFU (int[][] operators, int k) {
        // write code here
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < operators.length; i++) {
            if (operators[i][0] == 1) {
                put(operators[i][1], operators[i][2], k);
            } else {
                res.add(get(operators[i][1]));
            }
        }
        return res.stream().mapToInt(Integer::intValue).toArray();
    }

    public int get(int key) {
        //如果当前key存在于nodeMap,到其中去找
        if (nodeMap.containsKey(key)) {
            LNode node = nodeMap.get(key);
            //从源频率双向链表中删除 并在新频率双向链表中加入
            DeLinkedList oriList = freqMap.get(node.freq);
            oriList.delete(node);
            if (minFreq == node.freq && oriList.size == 0) {
                minFreq += 1;
            }
            node.freq += 1;
            if (!freqMap.containsKey(node.freq)) {
                freqMap.put(node.freq, new DeLinkedList());
            }
            DeLinkedList curList = freqMap.get(node.freq);
            curList.add(node);
            return node.val;
        } else {
            return -1;
        }
    }

    public void put(int key, int val, int k) {
        if (nodeMap.containsKey(key)) {
            LNode node = nodeMap.get(key);
            DeLinkedList oriList = freqMap.get(node.freq);
            oriList.delete(node);
            if (minFreq == node.freq && oriList.size == 0) {
                minFreq += 1;
            }
            node.val = val;
            node.freq += 1;
            nodeMap.put(key, node);
            if (!freqMap.containsKey(node.freq)) {
                freqMap.put(node.freq, new DeLinkedList());
            }
            DeLinkedList curList = freqMap.get(node.freq);
            curList.add(node);
        } else {
            if (nodeMap.size() == k) {
                //删除最小出现频率链表中最不经常被访问的结点
                DeLinkedList minFreqList = freqMap.get(minFreq);
                LNode deleted = minFreqList.delete(minFreqList.head.next);
                if (minFreq == deleted.freq && minFreqList.size == 0) {
                    minFreq += 1;
                }
                nodeMap.remove(deleted.key, deleted);
            }
            LNode cur = new LNode(key, val);
            nodeMap.put(key, cur);
            if (!freqMap.containsKey(1)) {
                freqMap.put(1, new DeLinkedList());
            }
            freqMap.get(1).add(cur);
            if (minFreq > 1) {
                minFreq = 1;
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值