当前位置:   article > 正文

【算法题解】27. 实现一个LRU缓存_lru 算法题

lru 算法题

这是一道 中等难度 的题

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

题目

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

  • LRUCache(int capacity)正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 getput 必须以 O ( 1 ) O(1) O(1) 的平均时间复杂度运行。

示例:

输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

提示:

  • 1 < = c a p a c i t y < = 3000 1 <= capacity <= 3000 1<=capacity<=3000
  • 0 < = k e y < = 10000 0 <= key <= 10000 0<=key<=10000
  • 0 < = v a l u e < = 1 0 5 0 <= value <= 10^5 0<=value<=105
  • 最多调用 2 ∗ 1 0 5 2 * 10^5 2105getput

题解(哈希表 + 双向链表

解题之前先了解一下什么是 LRU缓存LRU 的英文全称为 Latest Recently Used,即 最近最少使用。 在缓存占满的时候,先删除最旧的数据。

缓存数据最简单的办法就是把数据放在一个 哈希表 中,但是因为哈希表是无序的,所以如果单靠哈希表来实现 删除最旧的数据 这一功能的话,那么就需要循环遍历整个哈希表去找出最旧的数据,时间复杂度是 O ( n ) O(n) O(n),是不可取的。

这个时候可以增加一个数据结构 双向链表,用空间换时间。

因为对于LRU缓存来说,超过容量时删除最旧的数据,但是也会存在删除中间数据的情况,如之前使用过的数据再次被使用时,就应该将其移动到最新的位置上,也就是在原来的位置删掉并添加到最新的位置上。所以数组和队列都是不合适的,无法使用 O ( 1 ) O(1) O(1)的时间复杂度删除任意位置的数据。

Java 代码实现
class LRUCache {

    private int capacity;

    private Map<Integer, ListNode> cache;

    // 保护节点,protectHead.next 为head节点, protectTail.pre为tail节点
    private ListNode protectHead = new ListNode();
    private ListNode protectTail = new ListNode();

    public LRUCache(int capacity) {
        this.capacity = capacity;
        cache = new HashMap<>(capacity);
        protectHead.next = protectTail;
        protectTail.pre = protectHead;
    }


    // 删除指定节点
    private void remove(ListNode listNode){
        listNode.pre.next = listNode.next;
        listNode.next.pre = listNode.pre;

        listNode.pre = null;
        listNode.next = null;
    }

    // 添加到末尾
    private void addToTail(ListNode listNode){

        protectTail.pre.next = listNode;
        listNode.pre = protectTail.pre;

        listNode.next = protectTail;
        protectTail.pre = listNode;
        
    }

    // 从当前位置移动到末尾
    private void moveToTail(ListNode listNode){
        
        this.remove(listNode);
        this.addToTail(listNode);
        
    }
    
    public int get(int key) {
        if(cache.containsKey(key)){
            ListNode listNode = cache.get(key);
            this.moveToTail(listNode);
            return listNode.value;
        }else{
            return -1;
        }

    }
    
    public void put(int key, int value) {
        if(cache.containsKey(key)){
            // 将 key 移动到最新的位置
            // 1. 在旧的位置删除
            // 2. 追加key到链表末尾
            ListNode listNode = cache.get(key);

            // 这里必须重新赋值,虽然缓冲已经存在了,但是可能值不一样。
            listNode.value = value;
            this.moveToTail(listNode);
            return;

        }

        if(cache.size() == capacity){
            // 1. 找到最旧的数据,也就是链表的head,删除head
            // 2. 在cache map 中删除 head对应的key
            ListNode headNode = protectHead.next;
            this.remove(headNode);
            cache.remove(headNode.key);
        }


        // 1. 添加新的key到cache map
        // 2. 追加新的key到链表末尾

        ListNode listNode = new ListNode();
        listNode.key = key;
        listNode.value = value;

        this.addToTail(listNode);
        cache.put(key, listNode);

    }
}

class ListNode{
    int key;
    int value;
    ListNode pre;
    ListNode next;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
Go 代码实现
// 定义双向链表
type MyListNode struct {
    key, value int
    pre, next *MyListNode
}

type LRUCache struct { 
    size, capacity int
    cache map[int]*MyListNode
    protectHead, protectTail *MyListNode
}


func Constructor(capacity int) LRUCache {
    lruCache := LRUCache{
        size: 0,
        capacity: capacity,
        cache: map[int]*MyListNode{},
        protectHead: &MyListNode{},
        protectTail: &MyListNode{},
    }

    lruCache.protectHead.next = lruCache.protectTail
    lruCache.protectTail.pre = lruCache.protectHead
    return lruCache
}


func (this *LRUCache) Get(key int) int {
    if listNode, ok := this.cache[key]; ok {
        this.moveToTail(listNode);
        return listNode.value;
    }else{
        return -1
    }
}


func (this *LRUCache) Put(key int, value int)  {
    if listNode, ok := this.cache[key]; ok {
        listNode.value = value;
        this.moveToTail(listNode);
        return;
    }
    if(this.size == this.capacity){
        headNode := this.protectHead.next;
        this.remove(headNode);
        delete(this.cache, headNode.key);
        this.size--
    }

    listNode := &MyListNode{
        key: key,
        value: value,
    }


    this.addToTail(listNode)
    this.cache[key] = listNode
    this.size++
}

// 删除指定节点
func (this *LRUCache) remove(listNode *MyListNode){
    listNode.pre.next = listNode.next;
    listNode.next.pre = listNode.pre;

    listNode.pre = nil;
    listNode.next = nil;
}


// 添加到末尾
func (this *LRUCache) addToTail(listNode *MyListNode){
    this.protectTail.pre.next = listNode
    listNode.pre = this.protectTail.pre

    listNode.next = this.protectTail
    this.protectTail.pre = listNode
}


// 从当前位置移动到末尾
func (this *LRUCache) moveToTail(listNode *MyListNode){
    this.remove(listNode);
    this.addToTail(listNode);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
复杂度分析
  • 时间复杂度 O ( 1 ) O(1) O(1)getput 时间复杂度均为 O ( 1 ) O(1) O(1)
  • 空间复杂度 O ( n ) O(n) O(n)n 为给定的容量 capacity,哈希表存放 n 对值,双向链表存放 n + 2个节点,忽略常量后空间复杂度为 O ( n ) O(n) O(n)
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/491697
推荐阅读
相关标签
  

闽ICP备14008679号