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();
    }
}
相关推荐
许彰午3 小时前
CacheSQL(五):桥接篇
java·数据库·缓存·系统架构
阿维的博客日记4 小时前
介绍一下Redisson的看门狗机制
java·redis·缓存
遇见~未来5 小时前
Token、输入输出与缓存——AI开发计费全解
人工智能·缓存
阿维的博客日记6 小时前
为什么会出现缓存删除失败的情况
缓存
阿维的博客日记6 小时前
Redis的旁路缓存策略和先删除缓存后更新数据库,先更新数据库后删除缓存,这三种策略之间有什么关系??
数据库·redis·缓存
风筝在晴天搁浅7 小时前
设置一个带超时时间的LRU缓存
缓存
AI进化营-智能译站7 小时前
ROS2 C++开发系列18-STL容器实战:deque缓存激光雷达数据|priority_queue调度任务
开发语言·c++·缓存·ai
卧室小白1 天前
Redis-哨兵模式
数据库·redis·缓存
卧室小白1 天前
redis-配置
数据库·redis·缓存