LeetCode-146-LRU缓存

题目

在这里插入图片描述

思路

  1. 看题意字面意思应该还是理解的,但是我记得要用双向链表+HashMap什么的,所以应该不像题目看起来这么简单,直接润去题解。
  2. 对于已经构建好的LRU缓存,操作解释如下
        //创建缓存长度为2的LRU缓存机制
        LRUCache cache = new LRUCache(2);
        // 可以把 cache 理解成一个队列
        // 假设左边是队头,右边是队尾
        // 最近使用的排在队头,久未使用的排在队尾
        // 圆括号表示键值对 (key, val)

        cache.put(1, 1);
        // cache = [(1, 1)]

        cache.put(2, 2);
        // cache = [(2, 2), (1, 1)]

        cache.get(1);       // 返回 1
        // cache = [(1, 1), (2, 2)]
        // 解释:因为最近访问了键 1,所以提前至队头
        // 返回键 1 对应的值 1

        cache.put(3, 3);
        // cache = [(3, 3), (1, 1)]
        // 解释:缓存容量已满,需要删除内容空出位置
        // 优先删除久未使用的数据,也就是队尾的数据
        // 然后把新的数据插入队头

        cache.get(2);       // 返回 -1 (未找到)
        // cache = [(3, 3), (1, 1)]
        // 解释:cache 中不存在键为 2 的数据

        cache.put(1, 4);
        // cache = [(1, 4), (3, 3)]
        // 解释:键 1 已存在,把原始值 1 覆盖为 4
        // 不要忘了也要将键值对提前到队头
  1. 可以看出来,比起算法,这道题意思更是让我们写数据结构,其中get和put都必须以O(1)的平均时间复杂度运行。
  2. 很明显,在插入删除上,可以考虑使用双向链表,而在数据的保存和查找上,可以考虑使用哈希表查找。结合前面两点,形成一个新的数据结构:哈希链表。
  3. 必须选择双向链表的原因是我们经常需要进行动态的删除,此时必须知道节点前驱的消息,所以双向链表会方便一点。

思路代码

双向链表Node节点

//创建双链表的节点类
class Node{
    public int key,val; //kay和value
    public Node next,prev; //前后两节点
    public Node(int k,int v){//创建函数
        this.key=k;
        this.val=v;
    }
}

根据节点创建双向链表

注意里面只写了从尾部添加的方法,所以默认为靠近尾部的为较新的,靠近头部的为较久的

//根据节点,创建双链表
class DoubleLink{
    //头尾虚节点 至于为什么需要虚节点 推测应该是因为头尾经常需要互换 所以使用虚节点方便计算
    private Node head,tail;
    //链表元素数
    private int size;

    //构造函数
    public DoubleLink(){
        //初始化双向链表的数据 头尾节点 和size
        head = new Node(0,0);
        tail = new Node(0,0);
        head.next=tail;
        tail.prev=head;
        size=0;
    }
    //在链表尾部添加节点 时间复杂度O(1)
    public void addLast(Node x){
        x.prev = tail.prev;
        x.next=tail;
        tail.prev.next=x;
        tail.prev=x;
        size++;
    }
    //删除链表中的元素 因为之前会用hashmap查询 所以x一定存在
    public void remove(Node x){
        x.next.prev = x.prev;
        x.prev.next = x.next;
        size--;
    }
    //删除链表的第一个节点,并返回该节点
    public Node removeFirst(){
        //注意特殊情况
        if(head.next==tail) return null;
//        head.next.next.prev = head;
//        head.next=head.next.next;
//        size--;
        //好像可以直接调用 不过像我上面这样写应该也可以?
        Node temp = head.next;
        remove(temp);
        return head;
    }
    //返回链表的size
    public int size() { return size; }
}

结合双向链表和HashMap创建LRUCache

class LRUCache {
    //哈希表 注意一下 哈希表需要和链表链接起来,因为链表可以存储key value 而查找的时候只需要知道key
    //所以哈希表定为<key Node>
    private HashMap<Integer,Node>map;
    //双向链表 存储具体数据
    private DoubleLink cache;
    //最大容量
    private int cap;

    public LRUCache(int capacity) {
        //创建函数时很明显 创建对应的哈希表 双向链表 设定容量
        this.cap = capacity;
        this.map = new HashMap<Integer,Node>();
        this.cache = new DoubleLink();
    }
}

写几个避免get和put函数直接操作双向链表和map的辅助函数

  1. 将key对应的节点提升为最近使用的节点
  2. 添加新元素
  3. 删除key
  4. 删除最久没使用的元素
    //将key对应的节点提升为最近使用的节点
    private void MakeRecently(int key){
        Node x = map.get(key);
        //从链表中删除这个节点 然后把这个节点重新加入队尾
        cache.remove(x);
        cache.addLast(x);
    }
    
    //添加新元素
    private void addRencently(int key,int val){
        //直接创建node
        Node x  = new Node(key,val);
        //put进map
        map.put(key,x);
        //在cache里进行修改
        cache.addLast(x);
    }
    //删除一个key
    private void DeleteKey(int key){
        //获取对应的node
        Node x = map.get(key);
        //哈希表里面删除
        map.remove(key);
        //cache里删除
        cache.remove(x);
    }
    //删除最久没使用的元素
    private void removeLastRecently(){
        //要先获取被cache删除的节点 需要在map里删除
        Node deleteNode = cache.removeFirst();
        //移除cache里的最后一个即可
        map.remove(deleteNode.key);
    }

get()和put()

    //题目要求为如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
    public int get(int key) {
        if(map.containsKey(key)){
            //首先提到最近使用的节点
            MakeRecently(key);
            return map.get(key).val;
        }else return -1;
    }
    //题目要求为如果关键字key已经存在,则变更其数据值为value
    // 如果不存在,则向缓存中插入该组key-value
    // 如果插入操作导致关键字数量超过capacity ,则应该 逐出 最久未使用的关键字。

    public void put(int key, int value) {
        //如果关键字存在
        if(map.containsKey(key)){
            //删除旧元素 添加新元素 因为写那个方法的时候没有判断重复 所以必须让它不重复
            DeleteKey(key);
            addRencently(key,value);
        }else { //判断当前cap和总容量的关系
            if(cache.size()==this.cap){
                removeLastRecently();
            }
            //加入
            addRencently(key,value);
        }
    }

总代码

import java.util.HashMap;

//创建双链表的节点类
class Node{
    public int key,val; //kay和value
    public Node next,prev; //前后两节点
    public Node(int k,int v){//创建函数
        this.key=k;
        this.val=v;
    }
}
//根据节点,创建双链表
class DoubleLink{
    //头尾虚节点 至于为什么需要虚节点 推测应该是因为头尾经常需要互换 所以使用虚节点方便计算
    private Node head,tail;
    //链表元素数
    private int size;

    //构造函数
    public DoubleLink(){
        //初始化双向链表的数据 头尾节点 和size
        head = new Node(0,0);
        tail = new Node(0,0);
        head.next=tail;
        tail.prev=head;
        size=0;
    }
    //在链表尾部添加节点 时间复杂度O(1)
    public void addLast(Node x){
        x.prev = tail.prev;
        x.next=tail;
        tail.prev.next=x;
        tail.prev=x;
        size++;
    }
    //删除链表中的元素 因为之前会用hashmap查询 所以x一定存在
    public void remove(Node x){
        x.next.prev = x.prev;
        x.prev.next = x.next;
        size--;
    }
    //删除链表的第一个节点,并返回删除的那个节点
    public Node removeFirst(){
        //注意特殊情况
        if(head.next==tail) return null;
//        head.next.next.prev = head;
//        head.next=head.next.next;
//        size--;
        //好像可以直接调用 不过像我上面这样写应该也可以?
        Node temp = head.next;
        remove(temp);
        return temp;
    }
    //返回链表的size
    public int size() { return size; }
}
public class LRUCache {
    //哈希表 注意一下 哈希表需要和链表链接起来,因为链表可以存储key value 而查找的时候只需要知道key
    //所以哈希表定为<key Node>
    private HashMap<Integer,Node>map;
    //双向链表 存储具体数据
    private DoubleLink cache;
    //最大容量
    private int cap;

    public LRUCache(int capacity) {
        //创建函数时很明显 创建对应的哈希表 双向链表 设定容量
        this.cap = capacity;
        this.map = new HashMap<Integer,Node>();
        this.cache = new DoubleLink();
    }
    //将key对应的节点提升为最近使用的节点
    private void MakeRecently(int key){
        Node x = map.get(key);
        //从链表中删除这个节点 然后把这个节点重新加入队尾
        cache.remove(x);
        cache.addLast(x);
    }
    //添加新元素
    private void addRencently(int key,int val){
        //直接创建node
        Node x  = new Node(key,val);
        //put进map
        map.put(key,x);
        //在cache里进行修改
        cache.addLast(x);
    }
    //删除一个key
    private void DeleteKey(int key){
        //获取对应的node
        Node x = map.get(key);
        //哈希表里面删除
        map.remove(key);
        //cache里删除
        cache.remove(x);
    }
    //删除最久没使用的元素
    private void removeLastRecently(){
        //要先获取被cache删除的节点 需要在map里删除
        Node deleteNode = cache.removeFirst();
        //移除cache里的最后一个即可
        map.remove(deleteNode.key);
    }

    //题目要求为如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
    public int get(int key) {
        if(map.containsKey(key)){
            //首先提到最近使用的节点
            MakeRecently(key);
            return map.get(key).val;
        }else return -1;
    }
    //题目要求为如果关键字key已经存在,则变更其数据值为value
    // 如果不存在,则向缓存中插入该组key-value
    // 如果插入操作导致关键字数量超过capacity ,则应该 逐出 最久未使用的关键字。

    public void put(int key, int value) {
        //如果关键字存在
        if(map.containsKey(key)){
            //删除旧元素 添加新元素 因为写那个方法的时候没有判断重复 所以必须让它不重复
            DeleteKey(key);
            addRencently(key,value);
        }else { //判断当前cap和总容量的关系
            if(cache.size()==this.cap){
                removeLastRecently();
            }
            //加入
            addRencently(key,value);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值