算法-leetcode-hash表- 146. LRU 缓存

文章目录

    • 18, `146. LRU 缓存`
      • 思路1: 通过继承java中linkedHashmap直接实现
      • 思路2: 通过自定义方法实现:hashmap + 双链表

18, 146. LRU 缓存

https://leetcode-cn.com/problems/lru-cache/

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。

实现 LRUCache 类:

  • LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

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

思路1: 通过继承java中linkedHashmap直接实现

package com.shangguigu.dachang.algrithm.A06_hashTable;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author : 不二
 * @date : 2022/4/14-下午9:16
 * @desc : 146. LRU 缓存
 * https://leetcode-cn.com/problems/lru-cache/
 *
 **/
public class A63_LRUCacheWithLinkedHashMap extends LinkedHashMap<Integer, Integer> {

    public static void main(String[] args) {
        A63_LRUCacheWithLinkedHashMap lRUCache = new A63_LRUCacheWithLinkedHashMap(2);

        lRUCache.put(1, 1); // 缓存是 {1=1}
        lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
        System.out.println(lRUCache.get(1));   // 返回 1
        lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
        System.out.println(lRUCache.get(2));;    // 返回 -1 (未找到)
        lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
        lRUCache.get(1);    // 返回 -1 (未找到)
        lRUCache.get(3);    // 返回 3
        lRUCache.get(4);    // 返回 4

    }

    private int capacity;

    // 构造方式
    public A63_LRUCacheWithLinkedHashMap(int capacity) {
        // accessOrder: 如果key被访问,是否把当前key放置在最前位置
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }

    @Override
    public Integer get(Object key) {
        if (super.get(key) == null) {
            return -1;
        }
        return super.get(key);
    }

    @Override
    public Integer put(Integer key, Integer value) {
        return super.put(key, value);
    }

    // hashmap默认情况下当数据变多的时候,会自动扩充容量
    // put或者putAll的时候会调用该方法
    // 如果是false,是不会删除的数据的
    // 如果是true,则会删除最老的数据
    // 我们这里当容量大于指定容量的时候,需要删除掉最老的数据
    @Override
    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
        // size()是父类的一个方法,计算当前大小
        // 当前数据大于指定容量的时候,true:删除掉最老的数据
        return size() > capacity;
    }
}

思路2: 通过自定义方法实现:hashmap + 双链表

package com.shangguigu.dachang.algrithm.A06_hashTable;

import java.util.HashMap;

/**
 * @author : 不二
 * @date : 2022/4/15-下午1:56
 * @desc : 146. LRU 缓存
 * https://leetcode-cn.com/problems/lru-cache/
 **/
public class A65_LRUCache_Custom {
    public static void main(String[] args) {

        A65_LRUCache_Custom lRUCache = new A65_LRUCache_Custom(2);

        lRUCache.put(1, 1); // 缓存是 {1=1}
        lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
        System.out.println(lRUCache.get(1));   // 返回 1
        lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
        System.out.println(lRUCache.get(2));;    // 返回 -1 (未找到)
        lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
        System.out.println(lRUCache.get(1));;    // 返回 -1 (未找到)
        System.out.println(lRUCache.get(3));;    // 返回 3
        System.out.println(lRUCache.get(4));;    // 返回 4

    }

    // 定义双向链表节点, 用于存储数据之间的关系,方便后续删除
    // 这些节点都是存储在hashmap中方便删除更新
    // 然后节点是双向当,方便进行更改位置和找到老数据等等
    class Node {
        int key;
        int value;
        Node next;
        Node prev;

        // 方便进行构造
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }

        // 方便哨兵节点创建
        public Node() {
        }
    }

    private Node head, tail;

    // 定义hash表,用于存储对应的数据的媒介
    private HashMap<Integer, Node> hashMap = new HashMap<>();

    // 容量大小,当数据个数大于指定个数时候, 删除最老数据
    private int capacity;
    // 当前数据的个数
    private int size;


    // 构造LRUCache
    public A65_LRUCache_Custom(int capacity) {
        this.capacity = capacity;
        this.size = 0;
        // 把头尾的哨兵节点定义好
        head = new Node();
        tail = new Node();

        head.next = tail;
        tail.prev = head;
    }


    // get方法
    public Integer get(int key) {
        Node node = hashMap.get(key);
        if (node == null) {
            return -1;
        }

        // 如果存在,需要把当前node移动到末尾
        moveNodeToTail(node);
        return node.value;
    }

    public void put(int key, int value) {
        Node node = hashMap.get(key);
        if (node != null) {
            // 如果存在,需要更新数据
            node.value = value;
            // 并把这个节点放到链表到最后
            moveNodeToTail(node);
        } else {
            // 说明该节点不存在
            Node newNode = new Node(key, value);
            hashMap.put(key, newNode);

            addNodeToTail(newNode);

            // 直接封装成方法
            /*// 并把node放到链表最后
            tail.prev.next = newNode;
            newNode.next = tail;

            // 这里到时候, tail的前一个节点还是原先的那个,这里通过newNode.pre 指向 原先那个, 把原先最后一个变成倒数第二个
            newNode.prev = tail.prev;
            tail.prev = newNode;*/

            // 当前数据++
            size++;

            // 如果超出了容量限制,需要删除链表头数据
            if (size > capacity) {
                Node firstNode = removeFirstNode();
                hashMap.remove(firstNode.key);
                size--;
            }
        }
    }

    private void moveNodeToTail(Node newNode) {
        // Node theNode = newNode;

        // 把需要移动的节点的前一个节点指向后一个节点
        // 其实就是删除一个节点
        removeNode(newNode);

        // 然后把newNode添加在末尾即可
        addNodeToTail(newNode);
    }

    private void addNodeToTail(Node newNode) {
        /*// 并把node放到链表最后
        tail.prev.next = newNode;
        newNode.next = tail;

        // 这里到时候, tail的前一个节点还是原先的那个,这里通过newNode.pre 指向 原先那个, 把原先最后一个变成倒数第二个
        newNode.prev = tail.prev;
        tail.prev = newNode;*/

        // 换一种更简单的思路
        newNode.prev = tail.prev;
        newNode.next = tail;

        newNode.prev.next = newNode;
        newNode.next.prev = newNode;

    }

    private Node removeFirstNode() {
        Node firstNode = head.next;
        // 这里直接更改也行
        /*head.next = head.next.next;
        head.next.prev = head;*/
        // 这里直接拿到第一个节点,删除
        removeNode(firstNode);
        return firstNode;
    }

    // 删除某个节点
    private void removeNode(Node node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }
}

你可能感兴趣的:(算法,算法,java,leetcode)