leetcode 146. LRU缓存机制

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

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

进阶:

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

示例:

LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得关键字 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得关键字 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4


要让 put 和 get ⽅法的时间复杂度为 O(1),我们可以总结出 cache 这个数据结构必要的条件:查找快,插⼊快,删除快,有顺序之分。

因为显然 cache 必须有顺序之分,以区分最近使⽤的和久未使⽤的数据;⽽且我们要在 cache 中查找键是否已存在;如果容量满了要删除最后⼀个数据;每次访问还要把数据插⼊到队头。
那么,什么数据结构同时符合上述条件呢?哈希表查找快,但是数据⽆固定顺序;链表有顺序之分,插⼊删除快,但是查找慢。所以结合⼀下,形成⼀种新的数据结构:哈希链表。
LRU 缓存算法的核⼼数据结构就是哈希链表,双向链表和哈希表的结合体。这个数据结构⻓这样:
在这里插入图片描述


注意,这里插入尾部的时候需要注意操作顺序,顺序不对,功夫白费
先左pre,右next,插入进去。然后再左next,右pre连接。

每次访问一个值,需要将这个值移动到尾部。使用双向链表的好处就是方便插入,删除。


class ListNode:#建立一个双向的链表
    def __init__(self, key=None, value=None):
        self.key = key
        self.value = value
        self.pre = None
        self.next = None

class LRUCache:

    def __init__(self, capacity: int):
        self.capacity = capacity
        self.hashmap = {}
        #建立两个新节点 head and tail
        self.head = ListNode()
        self.tail = ListNode()
        #初始化链表 head<->tail
        self.head.next = self.tail
        self.tail.pre = self.head

    def move_node_to_tail(self, key):
        #找到哈希键的节点,移除key连接左右
        node = self.hashmap[key]
        node.pre.next = node.next
        node.next.pre = node.pre
        #将node节点插入到最后,注意顺序
        node.pre = self.tail.pre
        node.next = self.tail
        self.tail.pre.next = node
        self.tail.pre = node 


    def get(self, key: int) -> int:
        if key in self.hashmap:#如果该值在字典中,则移动到最后一个
            self.move_node_to_tail(key)
        res = self.hashmap.get(key, -1)#不存在返回-1,存在返回value
        if res == -1:
            return res
        else:
            return res.value

    def put(self, key: int, value: int) -> None:
        if key in self.hashmap:
            self.hashmap[key].value = value
            self.move_node_to_tail(key)
        else:#如果put的键值不在hashmap,则根据容量来看怎么加入
            if self.capacity == len(self.hashmap):#如果容量已满,删除最不常用的,即堆顶,在连接
                self.hashmap.pop(self.head.next.key)
                self.head.next = self.head.next.next
                self.head.next.pre = self.head
            #容量未满,插入到尾部
            new = ListNode(key, value)
            self.hashmap[key] = new
            #注意,这里插入尾部的时候需要注意操作顺序,顺序不对,功夫白费
            #先左pre,右next,插入进去。然后再左next,右pre连接
            new.pre = self.tail.pre
            new.next = self.tail
            self.tail.pre.next = new
            self.tail.pre = new

# SyntaxError: invalid character in identifier
#                         ^
#     def move_node_to_tail(self, key):
# Line 13  (Solution.py)
# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)

Java实现:
题目描述
设计LRU缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能
set(key, value):将记录(key, value)插入该结构
get(key):返回key对应的value值
[要求]
set和get方法的时间复杂度为O(1)
某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的。
当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。
若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1
对于每个操作2,输出一个答案

示例1
输入
复制
[[1,1,1],[1,2,2],[1,3,2],[2,1],[1,4,4],[2,2]],3
返回值
复制
[1,-1]
说明
第一次操作后:最常使用的记录为("1", 1)
第二次操作后:最常使用的记录为("2", 2),("1", 1)变为最不常用的
第三次操作后:最常使用的记录为("3", 2),("1", 1)还是最不常用的
第四次操作后:最常用的记录为("1", 1),("2", 2)变为最不常用的
第五次操作后:大小超过了3,所以移除此时最不常使用的记录("2", 2),加入记录("4", 4),并且为最常使用的记录,然后("3", 2)变为最不常使用的记录
import java.util.*;


public class Solution {
    /**
     * lru design
     * @param operators int整型二维数组 the ops
     * @param k int整型 the k
     * @return int整型一维数组
     */
    public int[] LRU (int[][] operators, int k) {
        // write code here
        LRUCache lur = new LRUCache(k);
        ArrayList<Integer> list = new ArrayList<>();
        for(int i = 0; i < operators.length; i++){
            if (operators[i].length == 3){
                lur.put(operators[i][1], operators[i][2]);
            } else {
                list.add(lur.get(operators[i][1]));
            }
        }
        int[] ans = new int[list.size()];
        for(int i = 0; i < list.size(); i++){
            ans[i] = list.get(i);
        }
        return ans;
    }
    class LRUCache{
        private LinkedHashMap<Integer, Integer> cache;
        private int capacity;

        LRUCache(int capactity){
            cache = new LinkedHashMap<>(capactity);
            this.capacity = capactity;
        }

        public int get(int key){
            if (!cache.containsKey(key)){
                return -1;
            }
            int res = cache.get(key);
            cache.remove(key);
            cache.put(key, res);
            return res;
        }

        public void put(int key, int value){
            if (cache.size() == capacity){
                Set<Integer> keySet = cache.keySet();
                Iterator<Integer> it = keySet.iterator();
                cache.remove(it.next());
            }

            if(cache.containsKey(key)){
                cache.remove(key);
            }
            cache.put(key, value);
        }
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值