LRU和LFU缓存

96 阅读3分钟

LRU缓存

LRU缓存题目:146.LRU缓存

【题目】

请你设计并实现一个满足 [LRU (最近最少使用) 缓存]约束的数据结构。

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

函数 `get` 和 `put` 必须以 `O(1)` 的平均时间复杂度运行。

【解答】

class LRUCache {

    class DoubleNode{
        DoubleNode pre;
        DoubleNode next;
        Integer key;
        Integer val;

        public DoubleNode(){}

        public DoubleNode(Integer key,Integer val){
            this.key = key;
            this.val = val;
        }
    }
    // key:对应的入参key
    Map<Integer,DoubleNode> map = new HashMap<>();
    //当前总数
    int count=0;
    //最大值
    int max =0;
    //默认头尾节点,并不是具体的键值对,起到控制首尾的作用
    DoubleNode head;
    DoubleNode end;
    public LRUCache(int capacity) {
        this.max = capacity;
        head = new DoubleNode(-1,-1);
        end = new DoubleNode(-1,-1);
        head.next = end;
        end.pre =head;
    }

    //将当前节点移动到首位
    public void moveToHead(DoubleNode node){
        node.pre = head;
        node.next=head.next;
        head.next.pre = node;
        head.next=node;
    }

    //连接当前节点的前后节点(相当于移出当前节点)
    public void linkPreAndNext(DoubleNode node){
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }

    public int get(int key) {
        if(map.get(key)==null){
            return -1;
        }else{
            DoubleNode node = map.get(key);
            //先将当前节点的前后节点连接
            linkPreAndNext(node);
            //然后将当前节点移动到头部
            moveToHead(node);
            return node.val;
        }
    }

    public void put(int key, int value) {
        if(map.get(key)==null){
            DoubleNode node = new DoubleNode(key,value);
            //直接添加,无需删除
            if(count<max){
                map.put(key,node);
                moveToHead(node);
                count++;
            }else{//添加,且删除1个尾部
                //删除最后一个元素
                map.remove(end.pre.key);
                linkPreAndNext(end.pre);
                //插入新值
                map.put(key,node);
                moveToHead(node);
                //这里已经删除了1个了,不需要再把count++
                // count++;
            }
        }else{
            DoubleNode node = map.get(key);
            linkPreAndNext(node);
            moveToHead(node);
            node.val=value;
             map.put(key,node);
        }
    }
}

LFU缓存(leetcode:460)

LFU缓存题目:146.LFU缓存

【题目】

请你为 [最不经常使用(LFU)]缓存算法设计并实现数据结构。

实现 `LFUCache` 类:

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

为了确定最不常使用的键,可以为缓存中的每个键维护一个 **使用计数器** 。
使用计数最小的键是最久未使用的键。

当一个键首次插入到缓存中时,它的使用计数器被设置为 `1` (由于 put 操作)。
对缓存中的键执行 `get` 或 `put` 操作,使用计数器的值将会递增。

函数 `get` 和 `put` 必须以 `O(1)` 的平均时间复杂度运行。

【解答】

class LFUCache {

    int capacity;
    int globalTime;
    Map<Integer,Node> map;
    TreeSet<Node> set;
    public LFUCache(int capacity) {
        this.capacity=capacity;
        globalTime=0;
        map = new HashMap<>();
        set = new TreeSet<>();
    }

    public int get(int key) {
        if(map.get(key)==null){
            return -1;
        }
        //移除set中老缓存,
        //构建新缓存:时间戳给到当前node,然后次数+1
        //新缓存存入set和覆盖map中的缓存
        Node cur = map.get(key);
        set.remove(cur);
        cur.cnt++;
        cur.time=globalTime;
        globalTime++;
        set.add(cur);
        map.put(key,cur);
        return cur.value;
    }

    public void put(int key, int value) {
        //如果值已存在,肯定不会超出,直接覆盖即可
        if(map.containsKey(key)){
            Node cur = map.get(key);
            set.remove(cur);
            cur.value=value;
            cur.cnt++;
            cur.time = globalTime;
            globalTime++;
            map.put(key,cur);
            set.add(cur);
        }else{
            //判断是否要溢出,需要提前移除数据
            if(set.size()>=capacity){
                Node deleteNode = set.first();
                set.remove(deleteNode);
                map.remove(deleteNode.key);
            }
            Node cur = new Node(1,globalTime,key,value);
            globalTime++;
            set.add(cur);
            map.put(key,cur);
        }
    }

    class Node implements Comparable<Node>{

        int cnt,time,key,value;

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

        //次数越小排在越前面,次数相同时间最早排在最前
        public int compareTo(Node other){
            if(cnt==other.cnt){
                return time - other.time;
            }else {
                return cnt-other.cnt;
            }
        }

        public boolean equals(Object other){
            if(this == other){
                return true;
            }
            if(other instanceof Node){
                Node ot = (Node)other;
                return this.cnt==ot.cnt
                        && this.time ==ot.time;
            }
            return false;
        }

        public int hashCode(){
            return cnt*1000000007 + time;
        }
    }
}