LeetCode104LRU缓存机制_java实现(史上最通俗易懂)

LeetCode104:LRU缓存机制_java实现(史上最通俗易懂)

题目概述:

点我看原题

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作:

  • 获取数据 get 和 写入数据 put 。

  • 获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。

  • 写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

进阶:

你是否可以在 O(1) 时间复杂度内完成这两种操作?

思路分析:

什么是LRU:

​ LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。

分析思路

  1. 分析题目联想到LinkedHashMap,hashmap+一种特殊的数据结构,能够按照存入顺序且最近经常使用(LRU)原则来读取数据。

  2. 我们需要一个hashmap实现我们key,value关系。我们怎么形成排序的hashmap?

  3. 我们想到可以用链表实现顺序,如果想按照LRU,我们可以按照头插法,让最后一个节点是我们最久未访问的。

  4. 秒!

  5. 仔细思考,我们最近最少使用,虽然没有用计数器判断哪一个对象使用了多少次,每一次头插法让它获得了最不被淘汰的那一个。

  6. 可是如果我们链表满了该如何清除数据?当然根据LRU,我们每次头插法,那么尾节点是我们最不常用的。删除尾节点。

  7. 每一次访问,我们需要把权重增加,就是通过将数据插到第一个位置,保证我们访问的数据尊重LRU。

综上所述:

  • 加入元素,不仅加入hashmap中,还要加入到我们链表中,并且才用头插法,如果链表满了,我们需要清除最后一个。

  • 获取元素,不仅仅是返回元素,我们还需要把他移动到第一个元素位置,代表我们尽量不淘汰它,因为它刚才使用了。

  • 淘汰元素,我们淘汰最后一个元素,就是最早加入队伍,却最近没有调用的。

代码实现:



public class LRUCache {

    // 实现一个lru map 类似于linkedMap
    // 手写一个双向链表
    class MyNode {
        // 实现key value 关系
        int key, value;
        // 双向链表有两个构造方法
        MyNode() { }
        MyNode(int key, int value) {
            this.key = key;
            this.value = value;
        }
        // 有前指针和后指针
        MyNode preNode, nextNode;
    }
    // 当前容量,
    private int size = 0;
    // 容器大小,
    private int capacity;
    // 缓存map,存真实的,无序的数据
    private HashMap<Integer, MyNode> cache;
    // 头节点尾节点
    private MyNode head, tail;

    // 初始化我们的类
    LRUCache(int capacity) {
        this.capacity = capacity;
        head = new MyNode();
        tail = new MyNode();
        // 刚开始头节点的下一个是尾节点
        head.nextNode = tail;
        // 尾节点的前一个是头节点
        tail.preNode = head;
        cache = new HashMap<>();
    }

    // 实现链表的 加入操作
    public void put(int key, int value) {
        // 在缓存中获取key
        MyNode node = cache.get(key);
        // 如果对应的key存在值,则覆盖
        if (node != null) {
            node.value = value;
            // 移动它到队首,删除再移动
            movePer(node);
        } else {
            // 如果对应的key不存在值,则进行缓存
            MyNode newNode = new MyNode(key,value);
            cache.put(key, newNode);
            // 把key放入队首
            moveToHead(newNode);
            // 我们有序链表数据量加一
            ++size;
            // 如果队溢出了,则释放最后一个
            if (size > capacity) {
                // 释放尾元素
                MyNode tail = removeTail();
                // 释放缓存中的元素
                cache.remove(tail.key);
                // 容量减少
                --size;
            }
        }
    }

    // 实现链表的 获取操作
    public int get(int key) {
        // 如果key不存在,
        MyNode node = cache.get(key);
        if (node == null) {
            return -1;
        }
        // 存在key的话,把它移动到首节点,删除再移动
        movePer(node);
        //返回key对应的value
        return node.value;
    }

    // 把对应的key放入队首
    private void movePer(MyNode node) {
        // 先删除
        deleteNode(node);
        // 再移动
        moveToHead(node);
    }
    private void moveToHead(MyNode node){
        // 该元素的前一个元素应该是头节点
        node.preNode = head;
        // 该元素的后一个元素应该是刚才第一个节点
        node.nextNode = head.nextNode;
        // 刚才第一个节点的前节点应该是该元素,不能写在下一句代码的后面
        head.nextNode.preNode = node;
        // 头节点应该指向该节点
        head.nextNode = node;
    }

    // 释放尾元素
    private MyNode removeTail() {
        // 获取最后一个元素
        MyNode lastNode = tail.preNode;
        // 删除元素
        deleteNode(lastNode);
        // 返回最后一个元素
        return lastNode;
    }

    // 删除元素
    private void deleteNode(MyNode node) {
        // 该元素的前一个元素因该指向该元素的后一个元素
        node.preNode.nextNode = node.nextNode;
        // 后一个元素的前元素应该是该元素的前元素
        node.nextNode.preNode = node.preNode;
    }

    public static void main(String[] args) {
        LRUCache lruCache = new LRUCache(2);
        lruCache.put(1, 1);
        lruCache.put(2, 2);
        lruCache.put(2, 3);
        System.out.println(lruCache.get(2));
    }

}

我没讲述明白的欢迎评论或私信

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值