LeetCode146 手撕LRU算法的2种实现方法

在这里插入图片描述

最近最久未使用 如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小
1.用一个数组来存储数据,给每一个数据项标记一个访问时间戳,每次插入新数据项的时候,先把数组中存在的数据项的时间戳自增,并将新数据项的时间戳置为0并插入到数组中。每次访问数组中的数据项的时候,将被访问的数据项的时间戳置为0。当数组空间已满时,将时间戳最大的数据项淘汰。

2.利用一个链表来实现,每次新插入数据的时候将新数据插到链表的头部;每次缓存命中(即数据被访问),则将数据移到链表头部;那么当链表满的时候,就将链表尾部的数据丢弃。

3.利用链表和hashmap。当需要插入新的数据项的时候,如果新数据项在链表中存在(一般称为命中),则把该节点移到链表头部,如果不存在,则新建一个节点,放到链表头部,若缓存满了,则把链表最后一个节点删除即可。在访问数据的时候,如果数据项在链表中存在,则把该节点移到链表头部,否则返回-1。这样一来在链表尾部的节点就是最近最久未访问的数据项。

对于第一种方法, 需要不停地维护数据项的访问时间戳,另外,在插入数据、删除数据以及访问数据时,时间复杂度都是O(n)。对于第二种方法,链表在定位数据的时候时间复杂度为O(n)。所以在一般使用第三种方式来是实现LRU算法。
实现方案

4.使用LinkedHashMap实现
LinkedHashMap底层就是用的HashMap加双链表实现的,而且本身已经实现了按照访问顺序的存储。此外,LinkedHashMap中本身就实现了一个方法removeEldestEntry用于判断是否需要移除最不常读取的数,方法默认是直接返回false,不会移除元素,所以需要重写该方法。即当缓存满后就移除最不常用的数。

class LRUCache extends LinkedHashMap<Integer, Integer>{
    
    
    private int capacity;
    
    public LRUCache(int capacity) {
    
    
        super(capacity, 0.75F, true);
        this.capacity = capacity;
    }

    public int get(int key) {
    
    
        return super.getOrDefault(key, -1);
    }

    public void put(int key, int value) {
    
    
        super.put(key, value);
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
    
    
        return size() > capacity; 
    }
}
class LRUCache {
    
    
    // 最大容量
    int cap;
    // LUR的关键:哈希链表 
    LinkedHashMap<Integer,Integer> cache = new LinkedHashMap<>();

    public LRUCache(int capacity) {
    
    
        this.cap = capacity;
    }
    
    public int get(int key) {
    
    
        if (!cache.containsKey(key)){
    
    
            return -1;
        }
        // 调用了,所以把key置为最近使用
        makeRencently(key);
        return cache.get(key);

    }
    
    public void put(int key, int value) {
    
    
        // 如果已经包含key,先修改key的值,再将它变为最近使用
        if(cache.containsKey(key)){
    
    
            // 修改key的值(因为key的唯一性,放入的时候回自动覆盖原来的value)
            cache.put(key,value);
            // 再将它变为最近使用
            makeRencently(key);
            return;
        }
        // 如果超过了储存量,则删除头节点(因为实现的是尾插入,所以头结点就是最久没有用的,删去)
        if (cache.size() >= this.cap){
    
    
            // 获取头结点
            // Map中所有的键存入到set集合中。因为set具备迭代器。所有可以迭代方式取出所有的键
            // 使用.iterator()迭代器后的指针其实指向的是第一个元素的上方,即指向一个空
            // .next()指针下移一位,指向头节点。hasNext方法的,判断下一个元素的有无,并不移动指针
            int oldestKey = cache.keySet().iterator().next();
            //删去头结点
            cache.remove(oldestKey);
        }
        // 放入,将新的key添加到链表尾部
        cache.put(key,value);
    }
    
    // 将节点移到链表尾部,变为最近使用
    public void makeRencently(int key){
    
    
        // 获取key对应的值
        int val = cache.get(key);
        // 删除key,重新插入队尾
        cache.remove(key);
        cache.put(key,val);
    }
}

==============================================
在这里插入图片描述

注意、以上都不是面试官想要看到的,需要自己实现双向链表+hashmap的映射关系。

只需要三步:
1.构建Node节点
2.构建双向链表
3.构建hashmap映射

一、Node节点

// 一、节点类
class Node {
    
    
    public int key, val;
    public Node next, prev; //下一个和前面一个
    public Node (int key, int val) {
    
    
        this.key = key;
        this.val = val;
    }
}

// 二、双端链表
class DoubleLinked {
    
    
    private Node head, tail;//头结点和尾结点
    private int size;//链表的元素数目

    public DoubleLinked() {
    
    
        head = new Node(0, 0);
        tail = new Node(0, 0);
        head.next = tail;
        tail.prev = head;
        size = 0;
    }

    // 在头部插入node结点
    public void addFirst(Node x) {
    
    
        x.prev = head;
        x.next = head.next;

        head.next.prev = x;
        head.next = x;
        size++;
    }

    // 移除指定结点
    public void remove(Node x) {
    
    
        x.prev.next = x.next;
        x.next.prev = x.prev;
        size--;
    }

    // 删除链表的第一个结点,并返回该结点
    public Node removeLast() {
    
    
        if(head.next == tail) return null;//返回空

        Node last = tail.prev;
        remove(last);//删除尾结点;
        return last;
    }

    public int size() {
    
    
        return size;
    }
}

// 三、构造hashMap映射
class LRUCache {
    
    
    HashMap<Integer, Node> map;
    DoubleLinked linked;
    int cap; //容量

    public LRUCache(int capacity) {
    
    
        map = new  HashMap<>();
        linked = new DoubleLinked();
        this.cap = capacity;
    }

    public int get(int key) {
    
    
        if(!map.containsKey(key)) return -1;
        int val = map.get(key).val;
        put(key, val);//放入头结点
        return val;
    }

    public void put(int key, int value) {
    
    
        Node x = new Node(key, value);
        if(map.containsKey(key)) {
    
    
            linked.remove(map.get(key));//移除结点
            linked.addFirst(x);
            map.put(key, x);
        }else {
    
    
            if(cap == cache.size()) {
    
    
                Node last = linked.removeLast();
                map.remove(last.key);
            }
            linked.addFirst(x);
            map.put(key, x);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/wyn_365/article/details/119713854