小朋友都能看懂的题解 | #设计LFU缓存结构#

什么是LFU缓存?

想象你有一个小书架,但你的书架只能放几本书。每次你想读一本书,如果书架上已经有这本书,你就直接拿起来读。但如果书架上没有这本书,而书架已经满了,你需要决定哪本书应该被取下来,以便腾出位置放新书。

LFU缓存就是帮助你决定哪本书应该被取下来。它会优先考虑那些很少被读的书,如果有多本书读的次数一样少,那么它会选择最早放在书架上的那本。📚📖

数据结构

  1. 键值对映射 (keyValueMap):这是一个大大的笔记本,用来记录每本书的名字和它的内容。比如,键是书的名字,值是书的内容。
  2. 键频率映射 (keyFreqMap):这是一个小本子,用来记录每本书被读了多少次。
  3. 频率集合映射 (freqMap):这是一个特殊的盒子,用来记录每个频率下有哪些书。这样我们可以快速找到频率最低的书。
  4. 最小堆 (minHeap):这是一个神奇的袋子,用来快速找到频率最低且最旧的书。每个元素是一个小纸条,上面写着书的名字、被读的次数和最后一次读的时间。

导入必要的包

import java.util.*;

这行代码导入了Java标准库中的所有必要类和接口,包括哈希表、集合、优先队列等。

定义 Solution 类和 LFU 方法

public class Solution {
    public int[] LFU(int[][] operators, int k) {
  • Solution 类是我们实现的主要类。
  • LFU 方法接受两个参数:
    • operators:一个二维数组,表示一系列操作。
    • k:缓存的最大容量。

初始化数据结构

        // 初始化我们的大本子和小本子
        Map<Integer, Integer> keyValueMap = new HashMap<>();
        Map<Integer, Integer> keyFreqMap = new HashMap<>();
        Map<Integer, Set<Integer>> freqMap = new HashMap<>();
        PriorityQueue<int[]> minHeap = new PriorityQueue<>((a, b) -> {
            if (a[1] != b[1]) return a[1] - b[1];
            return a[2] - b[2];
        });

        int timestamp = 0;
        List<Integer> results = new ArrayList<>();
  • keyValueMap:存储键和值的映射。
  • keyFreqMap:存储键和其被访问次数的映射。
  • freqMap:存储每个频率对应的键的集合。
  • minHeap:优先队列,用于快速找到频率最低且最旧的键。每个元素是一个数组 [key, frequency, timestamp]
  • timestamp:用于记录操作的时间。
  • results:存储所有 get 操作的结果。

处理每个操作

        for (int[] operator : operators) {
            int opt = operator[0];
            int key = operator[1];
  • 遍历每个操作 operator
  • opt 表示操作类型(1 表示 set,2 表示 get)。
  • key 是操作的键。

处理 set 操作

            if (opt == 1) { // 放一本书(set)
                int value = operator[2];
                if (keyValueMap.size() >= k && !keyValueMap.containsKey(key)) {
                    // 书架已满,需要取下一本最少读的书
                    while (!minHeap.isEmpty()) {
                        int[] entry = minHeap.poll();
                        int evictKey = entry[0];
                        if (keyFreqMap.get(evictKey) == entry[1]) {
                            keyValueMap.remove(evictKey);
                            keyFreqMap.remove(evictKey);
                            freqMap.get(entry[1]).remove(evictKey);
                            if (freqMap.get(entry[1]).isEmpty()) {
                                freqMap.remove(entry[1]);
                            }
                            break;
                        }
                    }
                }
                keyValueMap.put(key, value); // 把新书放到书架上
                keyFreqMap.put(key, 1); // 记录新书被读了1次
                freqMap.computeIfAbsent(1, f -> new HashSet<>()).add(key); // 把新书放进频率盒子里
                minHeap.offer(new int[]{key, 1, timestamp++}); // 把新书放进神奇袋子里
  • 获取操作的值 value
  • 如果书架已满且新书不在书架上,需要从 minHeap 中取出频率最低且最旧的键,并从所有数据结构中删除它。
  • 将新书放入 keyValueMap,设置其频率为 1,并更新 freqMapminHeap

处理 get 操作

            } else if (opt == 2) { // 拿一本书(get)
                if (keyValueMap.containsKey(key)) {
                    int value = keyValueMap.get(key); // 找到书的内容
                    int freq = keyFreqMap.get(key);
                    keyFreqMap.put(key, freq + 1); // 增加书的阅读次数
                    freqMap.get(freq).remove(key);
                    if (freqMap.get(freq).isEmpty()) {
                        freqMap.remove(freq);
                    }
                    freqMap.computeIfAbsent(freq + 1, f -> new HashSet<>()).add(key); // 更新频率盒子
                    minHeap.remove(new int[]{key, freq, -1});
                    minHeap.offer(new int[]{key, freq + 1, timestamp++}); // 更新神奇袋子
                    results.add(value); // 记录结果
                } else {
                    results.add(-1); // 书不在书架上,返回 -1
                }
            }
  • 如果键在 keyValueMap 中存在,获取其值并增加其频率。
  • 更新 keyFreqMapfreqMapminHeap
  • 将结果添加到 results 列表中。
  • 如果键不存在,返回 -1 并记录结果。

返回结果

        return results.stream().mapToInt(i -> i).toArray(); // 返回所有结果
    }
}
  • results 列表转换为数组并返回。

完整代码实现

import java.util.*;

public class Solution {
    public int[] LFU(int[][] operators, int k) {
        // 初始化我们的大本子和小本子
        Map<Integer, Integer> keyValueMap = new HashMap<>();
        Map<Integer, Integer> keyFreqMap = new HashMap<>();
        Map<Integer, Set<Integer>> freqMap = new HashMap<>();
        PriorityQueue<int[]> minHeap = new PriorityQueue<>((a, b) -> {
            if (a[1] != b[1]) return a[1] - b[1];
            return a[2] - b[2];
        });

        int timestamp = 0;
        List<Integer> results = new ArrayList<>();

        for (int[] operator : operators) {
            int opt = operator[0];
            int key = operator[1];

            if (opt == 1) { // 放一本书(set)
                int value = operator[2];
                if (keyValueMap.size() >= k && !keyValueMap.containsKey(key)) {
                    // 书架已满,需要取下一本最少读的书
                    while (!minHeap.isEmpty()) {
                        int[] entry = minHeap.poll();
                        int evictKey = entry[0];
                        if (keyFreqMap.get(evictKey) == entry[1]) {
                            keyValueMap.remove(evictKey);
                            keyFreqMap.remove(evictKey);
                            freqMap.get(entry[1]).remove(evictKey);
                            if (freqMap.get(entry[1]).isEmpty()) {
                                freqMap.remove(entry[1]);
                            }
                            break;
                        }
                    }
                }
                keyValueMap.put(key, value); // 把新书放到书架上
                keyFreqMap.put(key, 1); // 记录新书被读了1次
                freqMap.computeIfAbsent(1, f -> new HashSet<>()).add(key); // 把新书放进频率盒子里
                minHeap.offer(new int[]{key, 1, timestamp++}); // 把新书放进神奇袋子里
            } else if (opt == 2) { // 拿一本书(get)
                if (keyValueMap.containsKey(key)) {
                    int value = keyValueMap.get(key); // 找到书的内容
                    int freq = keyFreqMap.get(key);
                    keyFreqMap.put(key, freq + 1); // 增加书的阅读次数
                    freqMap.get(freq).remove(key);
                    if (freqMap.get(freq).isEmpty()) {
                        freqMap.remove(freq);
                    }
                    freqMap.computeIfAbsent(freq + 1, f -> new HashSet<>()).add(key); // 更新频率盒子
                    minHeap.remove(new int[]{key, freq, -1});
                    minHeap.offer(new int[]{key, freq + 1, timestamp++}); // 更新神奇袋子
                    results.add(value); // 记录结果
                } else {
                    results.add(-1); // 书不在书架上,返回 -1
                }
            }
        }

        return results.stream().mapToInt(i -> i).toArray(); // 返回所有结果
    }
}

希望这篇文章对你有帮助👍。

#牛客创作赏金赛##题解#
小学生都能看懂的算法 文章被收录于专栏

主要面向小白的算法文章。以小学生都能看懂为目标而编写,顺便巩固下自己。

全部评论

相关推荐

10-14 14:12
已编辑
少儿频道 算法工程师
反向练手了属于是
牛客71321951号:秋招的每一步都在成为小丑王的路上狂奔
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务