力扣146题、460题、牛客100(LRU缓存机制、LFU算法)

146.LRU缓存机制

基本思想:

需要做到两点:

1.

快读找到某个key是否有对应的val。(用字典)

2.

要支持在任意的位置快速插入和删除元素。(用链表)

结合起来就是哈希链表

使用双向链表和哈希表结合

为什么使用双向链表?

具体实现:

 

规定越靠近头部是最新的元素,越靠近尾部是越旧的元素

代码:

import java.util.*;

public class Solution {
    private int capacity;
    private Map<Integer, Node> map;
    private Node head;
    private Node tail;
    private int used;
    
    class Node {
        int key;
        int value;
        Node prev;
        Node next;
                
        Node(int key, int value, Node prev, Node next) {
            this.key = key;
            this.value = value;
            this.prev = prev;
            this.next = next;
        }
    }
    
    public Solution(int capacity) {
         // write code here
        this.capacity = capacity;
        this.map = new HashMap<>();
        this.used = 0;
    }

    public int get(int key) {
         // write code here
        if (!map.containsKey(key)) {
            return -1;
        }

        makeRecently(key);
        
        return map.get(key).value;
    }

    public void set(int key, int value) {
         // 如果 key 已存在,直接修改值,再移到链表头部
        if (map.containsKey(key)) {
            map.get(key).value = value;
            makeRecently(key);
            return;
        }
        
        // 如果达到容量上限,就要移除尾部节点,注意 HashMap 要 remove!!!
        if (used == capacity) {
            map.remove(tail.key);
            tail = tail.prev;
            tail.next = null;
            used--;
        }
        // 头节点为空,单独处理
        if (head == null) {
            head = new Node(key, value, null, null);
            tail = head;
        }
        else {
            Node t = new Node(key, value, null, head);
            head.prev = t;
            head = t;
        }
        map.put(key, head);
        
        used++;
    }
    
        // 把 key 对应的节点移到链表头部
    private void makeRecently(int key) {
        Node t = map.get(key);
        if (t != head) {
            if (t == tail) {
                tail = tail.prev;
                tail.next = null;
            }
            else {
                t.prev.next = t.next;
                t.next.prev = t.prev;
            }
            
            t.prev = null;
            t.next = head;
            head.prev = t;
            head = t;
        }
    }
}

/**
 * Your Solution object will be instantiated and called as such:
 * Solution solution = new Solution(capacity);
 * int output = solution.get(key);
 * solution.set(key,value);
 */

 

 

460、LFU算法

基本思想:

 

 

 具体实现:

定义两个字典:

1.第一个:key-to-node,

key对应的value

2.第二个:双向链表

freq对应key与value

目的:利用两个哈希表来使得两个操作的时间复杂度均为O(1)

get(key)操作:

判断key是否存在

存在的话,在原有的频率对应的列表[key:value;key:value;key:value]中,删除这个key:value

判断是否改变了最小频率

将这个key对应的freq+1

再将这key:value放入新的freq对应的列表中

put(key,value)操作:

插入的key如果原来就存在,做法如同get

插入的key不存在的话

判断容量是否满了,满了就删除最小频率对应的key

不满的话就是一个新的key,出现频率为1

 

 

 

代码:

import java.util.*;
public class Solution {
    //设置节点结构
    static class Node{ 
        int freq;
        int key;
        int val;
        //初始化
        public Node(int freq, int key, int val) {
            this.freq = freq;
            this.key = key;
            this.val = val;
        }
    }
    //频率到双向链表的哈希表
    private Map<Integer, LinkedList<Node> > freq_mp = new HashMap<>();
    //key到节点的哈希表
    private Map<Integer, Node> mp = new HashMap<>();
    //记录缓存剩余容量
    private int size = 0; 
    //记录当前最小频次
    private int min_freq = 0;
    
    public int[] LFU (int[][] operators, int k) {
        //构建初始化连接
        //链表剩余大小
        this.size = k;
        //获取操作数
        int len = (int)Arrays.stream(operators).filter(x -> x[0] == 2).count();
        int[] res = new int[len];
        //遍历所有操作
        for(int i = 0, j = 0; i < operators.length; i++){
            if(operators[i][0] == 1)
                //set操作
                set(operators[i][1], operators[i][2]);
            else
                //get操作
                res[j++] = get(operators[i][1]);
        }
        return res;
    }
    
    //调用函数时更新频率或者val值
    private void update(Node node, int key, int value) { 
        //找到频率
        int freq = node.freq;
        //原频率中删除该节点
        freq_mp.get(freq).remove(node); 
        //哈希表中该频率已无节点,直接删除
        if(freq_mp.get(freq).isEmpty()){ 
            freq_mp.remove(freq);
            //若当前频率为最小,最小频率加1
            if(min_freq == freq) 
                min_freq++;
        }
        if(!freq_mp.containsKey(freq + 1))
            freq_mp.put(freq + 1, new LinkedList<Node>());
        //插入频率加一的双向链表表头,链表中对应:freq key value
        freq_mp.get(freq + 1).addFirst(new Node(freq + 1, key, value)); 
        mp.put(key, freq_mp.get(freq + 1).getFirst());
    }
    
    //set操作函数
    private void set(int key, int value) {
        //在哈希表中找到key值
        if(mp.containsKey(key)) 
            //若是哈希表中有,则更新值与频率
            update(mp.get(key), key, value);
        else{ 
            //哈希表中没有,即链表中没有
            if(size == 0){
                //满容量取频率最低且最早的删掉
                int oldkey = freq_mp.get(min_freq).getLast().key; 
                //频率哈希表的链表中删除
                freq_mp.get(min_freq).removeLast(); 
                if(freq_mp.get(min_freq).isEmpty()) 
                    freq_mp.remove(min_freq); 
                //链表哈希表中删除
                mp.remove(oldkey); 
            }
            //若有空闲则直接加入,容量减1
            else 
                size--; 
            //最小频率置为1
            min_freq = 1; 
            //在频率为1的双向链表表头插入该键
            if(!freq_mp.containsKey(1))
                freq_mp.put(1, new LinkedList<Node>());
            freq_mp.get(1).addFirst(new Node(1, key, value)); 
            //哈希表key值指向链表中该位置
            mp.put(key, freq_mp.get(1).getFirst()); 
        }
    }
    
    //get操作函数
    private int get(int key) {
        int res = -1;
        //查找哈希表
        if(mp.containsKey(key)){ 
            Node node = mp.get(key);
            //根据哈希表直接获取值
            res = node.val;
            //更新频率 
            update(node, key, res); 
        }
        return res;
    }
}

 

posted @ 2021-03-05 10:39  最近饭吃的很多  阅读(257)  评论(0编辑  收藏  举报