【Leetcode哈希--双向链表】146.LRU 缓存机制 BM101.设计LFU缓存结构

文章目录

  • Leetcode146
    • 1.问题描述
    • 2.解决方案
      • 方法一:用时间点来确定删除节点,时间点最小的就是最近最少被使用
      • 方法二:哈希表 + 双向链表
  • BM101.设计LFU缓存结构
    • 1.问题描述
    • 2.解决方案

Leetcode146

1.问题描述

【Leetcode哈希--双向链表】146.LRU 缓存机制 BM101.设计LFU缓存结构_第1张图片
【Leetcode哈希--双向链表】146.LRU 缓存机制 BM101.设计LFU缓存结构_第2张图片

2.解决方案

方法一:用时间点来确定删除节点,时间点最小的就是最近最少被使用

class LRUCache {
private:
    //put和get都算使用
    unordered_map<int,int> uTime;
    int time;
    unordered_map<int,int> uMap;
    int capacity;
    int num;
public:
    LRUCache(int capacity) {
        this->capacity=capacity;
        this->num=0;
        this->time=0;
    }

    int get(int key) {
        if(uMap.find(key)!=uMap.end()){
            uTime[key]=time++;
            return uMap[key];
        }
        else return -1;
    }

    void put(int key, int value) {
        //如果关键字已存在变更value就可
        if(uMap.find(key)!=uMap.end()){
            uMap[key]=value;
            uTime[key]=time++;
            return;
        }
        //小于容量直接插入,并且更新成员变量
        if(num<capacity){
            uMap[key]=value;
            uTime[key]=time++;
            num++;
        }
        //如果大于就找到time最小的那个,证明最近没被操作过,删除
        else{
            int minKey=uTime.begin()->first;
            int minTime=uTime.begin()->second;
            for(unordered_map<int,int>::iterator it=uTime.begin()++;it!=uTime.end();it++){
                if(it->second<minTime){
                    minKey=it->first;
                    minTime=it->second;
                }
            }
            uMap.erase(minKey);
            uTime.erase(minKey);
            uMap[key]=value;
            uTime[key]=time++;
            num++;
        }
    }
};



方法二:哈希表 + 双向链表

思路说白了就是把使用到的key放到链表头节点
1.get操作算是使用到
2.put操作不管是key存不存在都算一次使用到
所以以上两种操作都会将节点放到头节点

【Leetcode哈希--双向链表】146.LRU 缓存机制 BM101.设计LFU缓存结构_第3张图片

代码实现方面注意点:
1.尽量把所有函数共有的功能提取出来
2.map的key和node的key是同一个key

带泛型Java版本

public class LRUCache {
    //双向链表节点
    class Node<K,V>{
        K key;
        V value;
        Node<K,V> next;
        Node<K,V> pre;
        public Node(){
            this.next=null;
            this.pre=null;
        }
        public Node(K k,V v){
            this.next=null;
            this.pre=null;
            this.key=k;
            this.value=v;
        }
    }
    //哈希表
    private Map<Integer,Node<Integer,Integer>> map;
    private Integer size;
    private Integer capacity;
    private Node<Integer,Integer> head;
    private Node<Integer,Integer> tail;

    //构造函数
    public LRUCache(int capacity){
        this.map=new HashMap<Integer,Node<Integer,Integer>>();
        this.size=0;
        this.capacity=capacity;
        this.head=new Node<Integer,Integer>();
        this.tail=new Node<Integer,Integer>();
        this.head.next=this.tail;
        this.tail.pre=this.head;
    }

    private void addToHead(Node<Integer,Integer> node){
        head.next.pre=node;
        node.next=head.next;
        head.next=node;
        node.pre=head;
    }

    private void removeNode(Node<Integer,Integer> node){
        node.pre.next=node.next;
        node.next.pre=node.pre;
    }

    private void moveTOHead(Node<Integer,Integer> node){
        removeNode(node);
        addToHead(node);
    }

    private Node<Integer,Integer> removeTail(){
        Node<Integer, Integer> node = this.tail.pre;
        removeNode(node);
        return node;
    }

    public Integer get(Integer key){
        Node<Integer,Integer> node = map.get(key);
        if(node==null) return -1;
        else{
            moveTOHead(node);
            return node.value;
        }
    }

    public void put(Integer key,Integer value){
        Node<Integer, Integer> node = map.get(key);
        if(node==null){
            Node<Integer, Integer> newNode = new Node<>(key, value);
            map.put(key,newNode);
            addToHead(newNode);

            this.size++;
            if(size>capacity){
                Node<Integer, Integer> tailNode = removeTail();
                map.remove(tailNode.key);
                size--;
            }
        }else{
            node.value=value;
            moveTOHead(node);
        }
    }
}

不带泛型Java版本

public class LRUCache {
    class DLinkedNode {
        int key;
        int value;
        DLinkedNode prev;
        DLinkedNode next;
        public DLinkedNode() {}
        public DLinkedNode(int _key, int _value) {key = _key; value = _value;}
    }

    private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
    private int size;
    private int capacity;
    private DLinkedNode head, tail;

    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        // 使用伪头部和伪尾部节点
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head.next = tail;
        tail.prev = head;
    }

    public int get(int key) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            return -1;
        }
        // 如果 key 存在,先通过哈希表定位,再移到头部
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            // 如果 key 不存在,创建一个新的节点
            DLinkedNode newNode = new DLinkedNode(key, value);
            // 添加进哈希表
            cache.put(key, newNode);
            // 添加至双向链表的头部
            addToHead(newNode);
            ++size;
            if (size > capacity) {
                // 如果超出容量,删除双向链表的尾部节点
                DLinkedNode tail = removeTail();
                // 删除哈希表中对应的项
                cache.remove(tail.key);
                --size;
            }
        }
        else {
            // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
            node.value = value;
            moveToHead(node);
        }
    }

    private void addToHead(DLinkedNode node) {
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }

    private void removeNode(DLinkedNode node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    private void moveToHead(DLinkedNode node) {
        removeNode(node);
        addToHead(node);
    }

    private DLinkedNode removeTail() {
        DLinkedNode res = tail.prev;
        removeNode(res);
        return res;
    }
}

手写不带泛型Java版本

问题1:这块不能new一个node放进cache中,因为你这样的话,后面要moveToHead(node),cache中的newNode的指针就没跟着变,就不一样了,导致后面可能就位置错位了,直接改变node的值就可以了

//存在key 更新
//注意点:这块不能new一个node放进cache中,因为你这样的话,后面要moveToHead(node),cache中的newNode的指针就没跟着变,就不一样了,导致后面可能就位置错位了
//cache.put(key, new ListNode(key, value, node.pre, node.next));
node.value = value;
moveToHead(node);

问题2:要注意cache中的node的指针关系要时时刻刻保持和链表中相同,否则get出来就会指针不一致,再去链表中操作,那就会出问题了

package leetcode;
import java.util.*;

class LRUCache {
    //public static void main(String[] args) {
    //    LRUCache lRUCache = new LRUCache(2);
    //    lRUCache.put(2, 1);
    //    lRUCache.put(2, 2);
    //    System.out.println(lRUCache.get(2));
    //    lRUCache.put(1, 1);
    //    lRUCache.put(4, 1);
    //    System.out.println(lRUCache.get(2));
    //}
    class ListNode {
        int key;
        int value;
        ListNode pre;
        ListNode next;
        ListNode(int key, int value){
            this.key = key;
            this.value = value;
            this.pre = null;
            this.next = null;
        }
        ListNode(int key, int value, ListNode pre, ListNode next){
            this.key = key;
            this.value = value;
            this.pre = pre;
            this.next = next;
        }
    }

    private int capacity;
    private int size;
    private ListNode DummyHead;
    private ListNode DummyTail;
    private Map<Integer, ListNode> cache = new HashMap<>();

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.size = 0;
        DummyHead = new ListNode(-1, -1, null, null);
        DummyTail = new ListNode(-1, -1, null, null);
        DummyHead.next = DummyTail;
        DummyTail.pre = DummyHead;
    }

    public void moveToHead(ListNode node){
        deleteNode(node);
        insertToHead(node);
    }

    public void deleteNode(ListNode node){
        //断开左右连接
        node.next.pre = node.pre;
        node.pre.next = node.next;
    }

    public void insertToHead(ListNode node){
        //头插
        ListNode t = DummyHead.next;
        DummyHead.next = node;
        t.pre = node;
        //注意点就是改变node的指针
        node.next = t;
        node.pre = DummyHead;
    }

    public int get(int key) {
        ListNode node = cache.get(key);
        if(node==null) return -1;
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        ListNode node = cache.get(key);
        if(node!=null){
            //存在key 更新
            //注意点:这块不能new一个node放进cache中,因为你这样的话,后面要moveToHead(node),cache中的newNode的指针就没跟着变,就不一样了,导致后面可能就位置错位了
            //cache.put(key, new ListNode(key, value, node.pre, node.next));
            node.value = value;
            moveToHead(node);
        }else{
            //不存在key 插入并且删除过期
            ListNode newNode = new ListNode(key, value);
            cache.put(key, newNode);
            insertToHead(newNode);
            this.size++;
            //while和if都可以
            while(size>0 && size>capacity){
                cache.remove(DummyTail.pre.key);
                deleteNode(DummyTail.pre);
                size--;
            }
            //if(size>capacity){
            //    cache.remove(DummyTail.pre.key);
            //    deleteNode(DummyTail.pre);
            //    size--;
            //}
        }
    }
}




BM101.设计LFU缓存结构

1.问题描述

【Leetcode哈希--双向链表】146.LRU 缓存机制 BM101.设计LFU缓存结构_第4张图片

2.解决方案

题解链接

【Leetcode哈希--双向链表】146.LRU 缓存机制 BM101.设计LFU缓存结构_第5张图片

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;
    }
}

你可能感兴趣的:(#,哈希,leetcode,缓存)