LFU缓存

题目要求:实现LFU(Least Frequently Used,最不经常使用)缓存逻辑,使用频次计数器进行淘汰。

后续更新


附代码:

java 复制代码
class LFUCache {

    // 双向链表节点
    private static class Node {
        int key, value;
        int freq = 1; // 访问频次
        Node prev, next;

        Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    // 频次链表(相同频次的节点组成一个双向链表)
    private static class FreqList {
        int freq;
        Node sentinel;
        int size; // 当前频次链表的节点数量
        FreqList prev, next;

        FreqList(int freq) {
            this.freq = freq;
            this.sentinel = new Node(0, 0);
            this.sentinel.prev = this.sentinel;
            this.sentinel.next = this.sentinel;
            this.size = 0;
        }

        // 判断当前频次链表是否为空
        boolean isEmpty() {
            return size == 0;
        }

        // 在链表头部添加节点
        void addFirst(Node node) {
            node.prev = sentinel;
            node.next = sentinel.next;
            sentinel.next.prev = node;
            sentinel.next = node;
            size++;
        }

        // 从链表中移除节点
        void removeNode(Node node) {
            node.prev.next = node.next;
            node.next.prev = node.prev;
            size--;
        }

        // 移除最后一个节点
        Node removeLast() {
            if (isEmpty()) return null;
            Node last = sentinel.prev;
            removeNode(last);
            return last;
        }
    }

    private final int capacity;
    private final Map<Integer, Node> keyToNode = new HashMap<>();
    private final Map<Integer, FreqList> freqToFreqList = new HashMap<>();
    private FreqList headFreqList; // 最小频次链表头

    public LFUCache(int capacity) {
        this.capacity = capacity;
        this.headFreqList = new FreqList(0);
    }

    public int get(int key) {
        if (!keyToNode.containsKey(key)) {
            return -1;
        }
        Node node = keyToNode.get(key);
        updateFreq(node);
        return node.value;
    }

    public void put(int key, int value) {
        if (capacity == 0) return;

        if (keyToNode.containsKey(key)) {
            Node node = keyToNode.get(key);
            node.value = value;
            updateFreq(node);
            return;
        }

        // 容量已满,淘汰最不常使用的节点
        if (keyToNode.size() >= capacity) {
            removeLeastFrequent();
        }

        // 创建新节点
        Node node = new Node(key, value);
        keyToNode.put(key, node);

        // 将节点放入频次为1的链表
        FreqList freqList = freqToFreqList.computeIfAbsent(1, k -> new FreqList(1));
        freqList.addFirst(node);

        // 如果1不是最小频次,更新最小频次链表头
        if (headFreqList.freq != 1) {
            // 插入到headFreqList之后
            freqList.next = headFreqList.next;
            if (headFreqList.next != null) {
                headFreqList.next.prev = freqList;
            }
            headFreqList.next = freqList;
            freqList.prev = headFreqList;
        }
    }

    // 更新节点的访问频次
    private void updateFreq(Node node) {
        int oldFreq = node.freq;
        int newFreq = oldFreq + 1;
        node.freq = newFreq;

        // 从旧频次链表中移除节点
        FreqList oldList = freqToFreqList.get(oldFreq);
        oldList.removeNode(node);

        // 将节点放入新频次链表
        FreqList newList = freqToFreqList.computeIfAbsent(newFreq, k -> new FreqList(newFreq));
        newList.addFirst(node);

        // 如果旧链表变空,从频次映射中移除并更新链表连接
        if (oldList.isEmpty()) {
            freqToFreqList.remove(oldFreq);
            removeFreqList(oldList);
        }

        // 更新最小频次链表头
        if (headFreqList.next == null || headFreqList.next.freq > newFreq) {
            // 不需要更新,因为新频次更大
        }
        if (headFreqList.next == null || headFreqList.next.freq > newFreq) {
            // 保持最小频次
        }
    }

    // 从双向频次链表中移除一个空的频次链表
    private void removeFreqList(FreqList freqList) {
        freqList.prev.next = freqList.next;
        if (freqList.next != null) {
            freqList.next.prev = freqList.prev;
        }
    }

    // 淘汰最不常使用的节点(最小频次且最久未使用)
    private void removeLeastFrequent() {
        if (headFreqList.next == null) return;

        // 找到最小频次链表(headFreqList.next 第一个非空的有效链表)
        FreqList minFreqList = headFreqList.next;
        while (minFreqList != null && minFreqList.isEmpty()) {
            minFreqList = minFreqList.next;
        }

        if (minFreqList == null) return;

        // 移除该链表的最后一个节点(最久未使用的)
        Node removed = minFreqList.removeLast();
        if (removed != null) {
            keyToNode.remove(removed.key);
        }

        // 如果链表变空,移除该链表
        if (minFreqList.isEmpty()) {
            removeFreqList(minFreqList);
            freqToFreqList.remove(minFreqList.freq);
        }
    }

    // 获取当前缓存大小
    public int size() {
        return keyToNode.size();
    }
}

ACM模式:

java 复制代码
import java.util.*;

class LFUCache {

    // 双向链表节点
    private static class Node {
        int key, value;
        int freq = 1; // 访问频次
        Node prev, next;

        Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    // 频次链表(相同频次的节点组成一个双向链表)
    private static class FreqList {
        int freq;
        Node sentinel;
        int size; // 当前频次链表的节点数量
        FreqList prev, next;

        FreqList(int freq) {
            this.freq = freq;
            this.sentinel = new Node(0, 0);
            this.sentinel.prev = this.sentinel;
            this.sentinel.next = this.sentinel;
            this.size = 0;
        }

        // 判断当前频次链表是否为空
        boolean isEmpty() {
            return size == 0;
        }

        // 在链表头部添加节点
        void addFirst(Node node) {
            node.prev = sentinel;
            node.next = sentinel.next;
            sentinel.next.prev = node;
            sentinel.next = node;
            size++;
        }

        // 从链表中移除节点
        void removeNode(Node node) {
            node.prev.next = node.next;
            node.next.prev = node.prev;
            size--;
        }

        // 移除最后一个节点
        Node removeLast() {
            if (isEmpty()) return null;
            Node last = sentinel.prev;
            removeNode(last);
            return last;
        }
    }

    private final int capacity;
    private final Map<Integer, Node> keyToNode = new HashMap<>();
    private final Map<Integer, FreqList> freqToFreqList = new HashMap<>();
    private FreqList headFreqList; // 最小频次链表头

    public LFUCache(int capacity) {
        this.capacity = capacity;
        this.headFreqList = new FreqList(0);
    }

    public int get(int key) {
        if (!keyToNode.containsKey(key)) {
            return -1;
        }
        Node node = keyToNode.get(key);
        updateFreq(node);
        return node.value;
    }

    public void put(int key, int value) {
        if (capacity == 0) return;

        if (keyToNode.containsKey(key)) {
            Node node = keyToNode.get(key);
            node.value = value;
            updateFreq(node);
            return;
        }

        // 容量已满,淘汰最不常使用的节点
        if (keyToNode.size() >= capacity) {
            removeLeastFrequent();
        }

        // 创建新节点
        Node node = new Node(key, value);
        keyToNode.put(key, node);

        // 将节点放入频次为1的链表
        FreqList freqList = freqToFreqList.computeIfAbsent(1, k -> new FreqList(1));
        freqList.addFirst(node);

        // 如果1不是最小频次,更新最小频次链表头
        if (headFreqList.freq != 1) {
            // 插入到headFreqList之后
            freqList.next = headFreqList.next;
            if (headFreqList.next != null) {
                headFreqList.next.prev = freqList;
            }
            headFreqList.next = freqList;
            freqList.prev = headFreqList;
        }
    }

    // 更新节点的访问频次
    private void updateFreq(Node node) {
        int oldFreq = node.freq;
        int newFreq = oldFreq + 1;
        node.freq = newFreq;

        // 从旧频次链表中移除节点
        FreqList oldList = freqToFreqList.get(oldFreq);
        oldList.removeNode(node);

        // 将节点放入新频次链表
        FreqList newList = freqToFreqList.computeIfAbsent(newFreq, k -> new FreqList(newFreq));
        newList.addFirst(node);

        // 如果旧链表变空,从频次映射中移除并更新链表连接
        if (oldList.isEmpty()) {
            freqToFreqList.remove(oldFreq);
            removeFreqList(oldList);
        }

        // 更新最小频次链表头
        if (headFreqList.next == null || headFreqList.next.freq > newFreq) {
            // 不需要更新,因为新频次更大
        }
        if (headFreqList.next == null || headFreqList.next.freq > newFreq) {
            // 保持最小频次
        }
    }

    // 从双向频次链表中移除一个空的频次链表
    private void removeFreqList(FreqList freqList) {
        freqList.prev.next = freqList.next;
        if (freqList.next != null) {
            freqList.next.prev = freqList.prev;
        }
    }

    // 淘汰最不常使用的节点(最小频次且最久未使用)
    private void removeLeastFrequent() {
        if (headFreqList.next == null) return;

        // 找到最小频次链表(headFreqList.next 第一个非空的有效链表)
        FreqList minFreqList = headFreqList.next;
        while (minFreqList != null && minFreqList.isEmpty()) {
            minFreqList = minFreqList.next;
        }

        if (minFreqList == null) return;

        // 移除该链表的最后一个节点(最久未使用的)
        Node removed = minFreqList.removeLast();
        if (removed != null) {
            keyToNode.remove(removed.key);
        }

        // 如果链表变空,移除该链表
        if (minFreqList.isEmpty()) {
            removeFreqList(minFreqList);
            freqToFreqList.remove(minFreqList.freq);
        }
    }

    // 获取当前缓存大小
    public int size() {
        return keyToNode.size();
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        int n = scanner.nextInt();
        LFUCache lfu = null;
        List<String> output = new ArrayList<>();

        for (int i = 0; i < n; i++) {
            String op = scanner.next();

            switch (op) {
                case "LFUCache":
                    int capacity = scanner.nextInt();
                    lfu = new LFUCache(capacity);
                    output.add("null");
                    break;

                case "put":
                    int key = scanner.nextInt();
                    int value = scanner.nextInt();
                    lfu.put(key, value);
                    output.add("null");
                    break;

                case "get":
                    int getKey = scanner.nextInt();
                    int result = lfu.get(getKey);
                    output.add(String.valueOf(result));
                    break;

                case "size":
                    output.add(String.valueOf(lfu.size()));
                    break;

                default:
                    output.add("unknown");
            }
        }

        // 输出结果
        for (int i = 0; i < output.size(); i++) {
            System.out.print(output.get(i));
            if (i < output.size() - 1) {
                System.out.print(" ");
            }
        }

        scanner.close();
    }
}
相关推荐
梅孔立5 小时前
两种免费的翻译API调用方式详解 - Edge官方API与个人缓存服务
前端·缓存·edge
gwf2165 小时前
SSD读写速度深度解析:顺序读写vs随机读写、IOPS、延迟,你的硬盘性能到底怎么看?
git·嵌入式硬件·缓存·github·智能硬件
番茄炒鸡蛋加糖8 小时前
缓存三大问题
缓存
江晓鱼未暖13 小时前
十七、Redis 核心原理与架构详解
大数据·数据库·数据仓库·redis·缓存·架构
小罗水13 小时前
第13章 Redis 缓存、幂等锁与任务状态
数据库·redis·缓存
热心市民lcj13 小时前
Spring Boot 整合 Caffeine 本地缓存实战
spring boot·后端·缓存
zx11545014 小时前
【热点 Key 探测与加长缓存 TTL 实战】
缓存
toooooop81 天前
如何用 ss + ps 精准定位本机 Redis 的“隐形”消费者?
linux·数据库·redis·缓存
软萌萌的11 天前
C#中的多级缓存架构设计与实现深度解析
缓存·c#·wpf
醉城夜风~1 天前
HTML表单域学习博客:表单标签、输入框、按钮详解
android·java·缓存