❗⭕⭕拆解LRU算法

一、题目

  • 题目描述

    设计LRU缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能

    1. set(key, value):将记录(key, value)插入该结构
    2. get(key):返回key对应的value值
  • 要求

    1. set和get方法的时间复杂度为O(1)
    2. 某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的。
    3. 当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。

若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1
对于每个操作2,输出一个答案

二、从底层开始搭建

在这里插入图片描述
为什么使用哈希链表?常数级别查找+前驱结点的获取

1. 双向链表节点类

class Node {
    public int key, val;
    public Node next, prev;
    public Node(int k, int v) {
        this.key = k;
        this.val = v;
    }
}

2. 双链表类

class DoubleList {  
    // 头尾虚节点
    private Node head, tail;  
    // 链表元素数
    private int size;

    public DoubleList() {
        // 初始化双向链表的数据
        head = new Node(0, 0);
        tail = new Node(0, 0);
        head.next = tail;
        tail.prev = head;
        size = 0;
    }

    // 在链表尾部添加节点 x,时间 O(1)
    public void addLast(Node x) {
        x.prev = tail.prev;
        x.next = tail;
        tail.prev.next = x;
        tail.prev = x;
        size++;
    }

    // 删除链表中的 x 节点(x 一定存在)
    // 由于是双链表且给的是目标 Node 节点,时间 O(1)
    public void remove(Node x) {
        x.prev.next = x.next;
        x.next.prev = x.prev;
        size--;
    }

    // 删除链表中第一个节点,并返回该节点,时间 O(1)
    public Node removeFirst() {
        if (head.next == tail)
            return null;
        Node first = head.next;
        remove(first);
        return first;
    }

    // 返回链表长度,时间 O(1)
    public int size() { return size; }

}

3. LRU类

class LRUCache {
    // key -> Node(key, val)
    private HashMap<Integer, Node> map;
    // Node(k1, v1) <-> Node(k2, v2)...
    private DoubleList cache;
    // 最大容量
    private int cap;

    public LRUCache(int capacity) {
        this.cap = capacity;
        map = new HashMap<>();
        cache = new DoubleList();
    }
    //封装get和put需要的函数
    /* 将某个 key 提升为最近使用的 */
	private void makeRecently(int key) {
	    Node x = map.get(key);
	    // 先从链表中删除这个节点
	    cache.remove(x);
	    // 重新插到队尾
	    cache.addLast(x);
	}
	
	/* 添加最近使用的元素 */
	private void addRecently(int key, int val) {
	    Node x = new Node(key, val);
	    // 链表尾部就是最近使用的元素
	    cache.addLast(x);
	    // 别忘了在 map 中添加 key 的映射
	    map.put(key, x);
	}
	
	/* 删除某一个 key */
	private void deleteKey(int key) {
	    Node x = map.get(key);
	    // 从链表中删除
	    cache.remove(x);
	    // 从 map 中删除
	    map.remove(key);
	}
	
	/* 删除最久未使用的元素 */
	private void removeLeastRecently() {
	    // 链表头部的第一个元素就是最久未使用的
	    Node deletedNode = cache.removeFirst();
	    // 同时别忘了从 map 中删除它的 key
	    int deletedKey = deletedNode.key;
	    map.remove(deletedKey);
	}
	
	//get和put
	public int get(int key) {
	    if (!map.containsKey(key)) {
	        return -1;
	    }
	    // 将该数据提升为最近使用的
	    makeRecently(key);
	    return map.get(key).val;
	}
	public void put(int key, int val) {
	    if (map.containsKey(key)) {
	        // 删除旧的数据
	        deleteKey(key);
	        // 新插入的数据为最近使用的数据
	        addRecently(key, val);
	        return;
	    }
	
	    if (cap == cache.size()) {
	        // 删除最久未使用的元素
	        removeLeastRecently();
	    }
	    // 添加为最近使用的元素
	    addRecently(key, val);
	}
}

三 、使用LinkedHashMap

class LRUCache {
    int cap;
    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 变为最近使用
        makeRecently(key);
        return cache.get(key);
    }

    public void put(int key, int val) {
        if (cache.containsKey(key)) {
            // 修改 key 的值
            cache.put(key, val);
            // 将 key 变为最近使用
            makeRecently(key);
            return;
        }

        if (cache.size() >= this.cap) {
            // 链表头部就是最久未使用的 key
            int oldestKey = cache.keySet().iterator().next();
            cache.remove(oldestKey);
        }
        // 将新的 key 添加链表尾部
        cache.put(key, val);
    }

    private void makeRecently(int key) {
        int val = cache.get(key);
        // 删除 key,重新插入到队尾
        cache.remove(key);
        cache.put(key, val);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zkFun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值