leetcode 146. LRU 缓存

题目链接
思路:双向链表+哈希表
分析:
这是一个很经典的策略,很多缓存淘汰策略都是采用的这个。
这里还要熟悉指针的操作。
这个题目的意思就是:

  1. 当键值对的个数没达到capacity的时候,那么就直接加入缓存。
  2. 当键值对的个数达到了capacity的时候,那么就要淘汰一个键值对。

这里存储键值对使用的是双向链表+哈希表。
双向链表(DouList)的结构如下:

	class DouList{
            //指向头节点
            DouListNode head;

            //指向尾节点
            DouListNode tail;

            //双向链表的总容量节点数
            int cap;

            //双向链表中实际的节点数
            int total;

            public DouList(int capacity){
                head=null;
                tail=null;
                cap=capacity;
            }
        }

双向链表中有头指针和尾指针,是为了方便操作。并且记录着容量的上限。

双向链表的节点(DouListNode)的数据结构如下:

class DouListNode{

            //用于在hash中删除key-val对
            int key;

            int val;

            //前置节点
            DouListNode pre;

            //后置节点
            DouListNode after;
            public DouListNode(int key, int val){
                this.key = key;
                this.val=val;
            }
        }

节点中记录着键值对(key-val),并且记录着前置节点和后置节点。

哈希表中的key:就是DouListNode中的key,
哈希表中的val:就是DouLikstNode

这里有两个问题:
问题1:当键值对的个数没有达到capacity的时候,是直接加入缓存,但是这里是要加入到最近使用的第一个位置。因为这是最近使用的。即加入时候是直接加入到双向链表的头部的。
. . . 问题1.1:加入的时候还要分两种情况
… …1. 链表中存在key值,那么要更新val值。
… …2. 链表中不存在key值,那么就是真正的插入。
问题2:当键值的个数达到了上限的时候,应该淘汰一个键值对,并且是双向链表的尾部节点。
然后又回到了第一个问题了,插入这个节点,因为刚刚已经淘汰了一个键值对,所以此时就又回到了没有到达上限的情况。

这里复杂一点的情况是:当插入节点的时候,这个节点已经存在了链表中。如图:
在这里插入图片描述
双向链表中已经存在了键值对(2-2),此时插入键值对(2-5)。
步骤:

  1. 首先通过哈希表判断是否存在这个key,这里判断出来是存在。

  2. 那么取出这个DouListNode,并且得到这个节点的前置节点。也就是键值对(1-1)。

  3. 将前置节点指向当前节点的后置节点。如下图
    在这里插入图片描述

  4. 并且将后置节点的前置指针指向当前节点的前置节点。如下图。
    在这里插入图片描述

  5. 此时,删除原来的键值对(2-2)节点,就成了如下图。
    在这里插入图片描述

  6. 此时将要插入的节点(2-5)插到头节点位置,也就是要实现一个头插法。
    将当前节点的后置指针指向原本的头节点。如下图
    在这里插入图片描述

  7. 将原来的头节点的前置指针指向当前节点。如图。
    在这里插入图片描述

  8. 将头指针指向当前节点。完毕。如图。
    在这里插入图片描述
    上面只是举例了一种较为复杂点的情况。完整代码如下。
    代码:

class LRUCache {

        //双向链表----
        //Hash表的val存的是Node

        class DouListNode{

            //用于在hash中删除key-val对
            int key;

            int val;

            //前置节点
            DouListNode pre;

            //后置节点
            DouListNode after;
            public DouListNode(int key, int val){
                this.key = key;
                this.val=val;
            }
        }

        class DouList{
            //指向头节点
            DouListNode head;

            //指向尾节点
            DouListNode tail;

            //双向链表的总容量节点数
            int cap;

            //双向链表中实际的节点数
            int total;

            public DouList(int capacity){
                head=null;
                tail=null;
                cap=capacity;
            }
        }

        private HashMap<Integer, DouListNode> map;
        private DouList dlist;

        public LRUCache(int capacity) {
            map=new HashMap<>();
            dlist = new DouList(capacity);
        }

        //每次get完,key不存在就不说了
        //将ket所对应的node节点放到头部,也就是头节点
        public int get(int key) {
            DouListNode node = map.get(key);
            if(node==null){
                return -1;
            }
            //将node放到头部节点去,并且长度不变
            changePlaceToHead(node);
            return node.val;
        }


        //将key-val放到map中,将node放到头节点
        public void put(int key, int value) {
            DouListNode node = new DouListNode(key,value);
            if(this.dlist.head==null){
                this.dlist.head = node;
                this.dlist.tail = node;
                this.dlist.total++;
                map.put(key, node);
                return;
            }else if(this.dlist.cap==1){
                map.remove(this.dlist.head.key);
                this.dlist.head = node;
                this.dlist.tail = node;
                this.dlist.total++;
                map.put(key, node);
                return;
            }
            if(!map.containsKey(key)){
                //此时本身没有包含key
                if(this.dlist.total<this.dlist.cap){
                    //双向链表还没放满   节点数+1
                    this.dlist.total++;
                }else{
                    //满了,删除最后一个节点,长度依旧是不变的,删除最后一个节点,又放入了一个节点,所以总的长度没变

                    //倒数第二个节点
                    DouListNode tailPre = this.dlist.tail.pre;

                    if(tailPre!=null){
                        tailPre.after = null;
                    }
                    //从map中删除key-val对
                    map.remove(this.dlist.tail.key);
                    //调整尾部节点为倒数第二个节点
                    this.dlist.tail = tailPre;
                }
                //将新的放进去
                map.put(key, node);

                //当前node就是新的头节点
                node.after = this.dlist.head;
                this.dlist.head.pre = node;
                this.dlist.head = node;
            }else{
                node = map.get(key);
                node.val = value;
                changePlaceToHead(node);
            }

        }


        //将node放到头部去
        private void changePlaceToHead(DouListNode node){
            //前置节点
            DouListNode preNode = node.pre;
            if(preNode==null){
                //说明自身就是头部节点
                return;
            }
            //后置节点
            DouListNode afterNode = node.after;

            //将前置节点的后置指针  指向 node的后置节点
            preNode.after = afterNode;//preNode->afterNode

            if(afterNode!=null){
                //如果后置节点不为null,那么将后置节点的前置指针  指向   node的前置节点
                afterNode.pre = preNode;// preNode<-afterNode
            }else{
                //如果当前节点的后置指针为null,说明node就是尾节点。那么移走了当前node,那么尾节点应该是当前节点的前置节点
                this.dlist.tail = preNode;//tail=preNode
            }

            //node为head,那么后置指针应该指向 以前的head,前置指针 为null
            node.after = this.dlist.head;//  ->head
            this.dlist.head.pre = node;//    <-head

            node.pre = null;
            this.dlist.head = node;//head=node
        }
    }

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值