LRU和LFU

LRU和LFU

LC156_LRU缓存

题目:实现一个LRU

示例:

  • LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4

解答:

在这里插入图片描述

public class LRUCache {

    private int size;
    private int capacity;
    private Map<Integer, DLinkedNode> cache;
    private DLinkedNode head;
    private DLinkedNode tail;

    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        this.cache = new HashMap<>(capacity);
        // 题目规定key、value>=0,这里可以传-1表示头尾结点
        this.head = new DLinkedNode(-1, -1);
        this.tail = new DLinkedNode(-1, -1);
        this.head.next = tail;
        this.tail.pre = head;
    }

    public int get(int key) {
        if (size == 0) {
            return -1;
        }
        DLinkedNode node = cache.get(key);
        if (node == null) {
            return -1;
        }

        deleteNode(node);
        removeToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
        if (node != null) {
            node.value = value;
            deleteNode(node);
            removeToHead(node);
            return;
        }

        if (size == capacity) {
            // 细节:容量满了,先清理缓存,再删除末尾结点
            cache.remove(tail.pre.key);
            deleteNode(tail.pre);
            size--;
        }

        node = new DLinkedNode(key, value);
        cache.put(key, node);
        // 更新缓存
        removeToHead(node);
        size++;
    }

    private void deleteNode(DLinkedNode node) {
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }

    private void removeToHead(DLinkedNode node) {
        node.next = head.next;
        head.next.pre = node;
        head.next = node;
        node.pre = head;
    }


    /**
     * 定义双向链表结点数据结构
     */
    private class DLinkedNode {
        int key;
        int value;
        DLinkedNode pre;
        DLinkedNode next;

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

        public DLinkedNode() {

        }

    }
}

LC460_LFU缓存

题目:实现一个LFU缓存

  • LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象
  • int get(int key) - 如果键 key 存在于缓存中,则获取键的值,否则返回 -1 。
  • void put(int key, int value) - 如果键 key 已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量 capacity 时,则应该在插入新项之前,移除最不经常使用的项。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近最久未使用 的键。

示例:

输入:
["LFUCache", "put", "put", "get", "put", "get", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [3], [4, 4], [1], [3], [4]]
输出:
[null, null, null, 1, null, -1, 3, null, -1, 3, 4]

解释:
// cnt(x) = 键 x 的使用计数
// cache=[] 将显示最后一次使用的顺序(最左边的元素是最近的)
LFUCache lfu = new LFUCache(2);
lfu.put(1, 1);   // cache=[1,_], cnt(1)=1
lfu.put(2, 2);   // cache=[2,1], cnt(2)=1, cnt(1)=1
lfu.get(1);      // 返回 1
                 // cache=[1,2], cnt(2)=1, cnt(1)=2
lfu.put(3, 3);   // 去除键 2 ,因为 cnt(2)=1 ,使用计数最小
                 // cache=[3,1], cnt(3)=1, cnt(1)=2
lfu.get(2);      // 返回 -1(未找到)
lfu.get(3);      // 返回 3
                 // cache=[3,1], cnt(3)=2, cnt(1)=2
lfu.put(4, 4);   // 去除键 1 ,1 和 3 的 cnt 相同,但 1 最久未使用
                 // cache=[4,3], cnt(4)=1, cnt(3)=2
lfu.get(1);      // 返回 -1(未找到)
lfu.get(3);      // 返回 3
                 // cache=[3,4], cnt(4)=1, cnt(3)=3
lfu.get(4);      // 返回 4
                 // cache=[3,4], cnt(4)=2, cnt(3)=3

解答:

在这里插入图片描述

class LFUCache {

    private int size;
    private int capacity;
    private Map<Integer, Node> cache;
    /**
     * 频次对应的双向链表
     */
    private Map<Integer, DoubleLinkedList> freqMap;
    /**
     * 当前最小值
     */
    private int min;

    public LFUCache(int capacity) {
        this.cache = new HashMap<>(capacity);
        this.freqMap = new HashMap<>();
        this.capacity = capacity;
    }

    public int get(int key) {
        Node node = cache.get(key);
        if (node == null) {
            return -1;
        }
        // node增加频次
        addFreq(node);
        return node.value;
    }

    public void put(int key, int value) {
        if (capacity == 0) {
            return;
        }
        Node node = cache.get(key);
        // node存在就更新频次
        if (node != null) {
            node.value = value;
            addFreq(node);
        } else {
            // node不存在
            // 链表满啦,移除最不经常使用的=移除min对应的链表
            if (size == capacity) {
                DoubleLinkedList minList = freqMap.get(min);
                cache.remove(minList.tail.pre.key);
                minList.removeNode(minList.tail.pre);
                size--;
            }

            node = new Node(key, value);
            cache.put(key, node);
            // 获取频次为1的链表
            DoubleLinkedList linkedList = freqMap.get(1);
            if (linkedList == null) {
                linkedList = new DoubleLinkedList();
                freqMap.put(1, linkedList);
            }
            linkedList.addNode(node);
            size++;
            // node不存在,更新最不长使用频次=1
            min = 1;
        }
    }

    private void addFreq(Node node) {
        // 从原freq对应的链表里移除, 并更新min
        int freq = node.freq;
        DoubleLinkedList linkedList = freqMap.get(freq);
        linkedList.removeNode(node);
        // freq = min 且 原freq对应的链表为空
        if (freq == min && linkedList.head.next == linkedList.tail) {
            min = freq + 1;
        }

        // 更新freq
        node.freq++;

        linkedList = freqMap.get(freq + 1);
        if (linkedList == null) {
            linkedList = new DoubleLinkedList();
            freqMap.put(freq + 1, linkedList);
        }

        linkedList.addNode(node);
    }

    class DoubleLinkedList {
        Node head;
        Node tail;

        public DoubleLinkedList() {
            head = new Node();
            tail = new Node();
            head.next = tail;
            tail.pre = head;
        }

        void removeNode(Node node) {
            node.pre.next = node.next;
            node.next.pre = node.pre;
        }

        void addNode(Node node) {
            node.next = head.next;
            head.next.pre = node;
            head.next = node;
            node.pre = head;
        }
    }

    /**
     * 结点
     */
    class Node {
        int key;
        int value;
        int freq = 1;
        Node pre;
        Node next;

        public Node() {
        }

        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值