手写LRU缓存

#一人分享一道面试手撕题#比较经典的题目,如果之前没写过的可能需要想一番,首先实现缓存最先想到的就是hashmap,O1级别的查找速度很适合做缓存,然后就是要实现lru,参考redis的zset底层实现,zset也是使用了两个数据结构跳表+hashmap,使用跳表实现排序,我们这里也是使用双向链表实现lru功能,每次查询对应数据的时候就将数据移除重新加到头部,也就是更新使用频率,附上代码如下
比较经典的题目,如果之前没写过的可能需要想一番,首先实现缓存最先想到的就是hashmap,O1级别的查找速度很适合做缓存,然后就是要实现lru,参考redis的zset底层实现,zset也是使用了两个数据结构跳表+hashmap,使用跳表实现排序,我们这里也是使用双向链表实现lru功能,每次查询对应数据的时候就将数据移除重新加到头部,也就是更新使用频率
//通过自定义节点,hashmap,哨兵节点
//删除时通过pre和next指针快速删除节点
//添加时只操作头尾,通过哨兵节点快速添加节点
class LRUCache {
    private class Node{
        //保留key,不然在删除尾结点的时候不能返回key让map也删除
        //而map又不知道尾结点是哪个
        int key,value;
        Node pre,next;
        Node(int key,int value){
            this.key=key;
            this.value=value;
        }
        Node(int key,int value,Node pre,Node next){
            this.key=key;
            this.value=value;
            this.pre=pre;
            this.next=next;
        }
    }
    private int capacity;
    //通过哨兵节点可以快速找到头结点和尾节点
    private Node dummy;
    //通过hashmap快速找到节点,通过节点pre指针和next指针快速实现删除
    private Map<Integer,Node> map=new HashMap();

    public LRUCache(int capacity) {
        this.capacity=capacity;
        //不能dummy=new Node(-1,-1,dummy,dummy)
        //这样会导致dummy的pre和next为null
        dummy=new Node(-1,-1);
        dummy.pre=dummy;
        dummy.next=dummy;
    }
    
    public int get(int key) {
        if(!map.containsKey(key)) return -1;
        //如果存在,更新使用频率(加到头部)
        Node node=map.get(key);
        remove(node);
        addFirst(node);
        return node.value;
    }
    
    public void put(int key, int value) {
        Node node;
        if(!map.containsKey(key)){
            while(map.size()>=capacity){
                int removeKey=removeLast();
                map.remove(removeKey);
            }
            node=new Node(key,value);
            addFirst(node);
        }else{
            //否则更新数值重新加入
            node=map.get(key);
            node.value=value;
            remove(node);
            addFirst(node);
        }
        map.put(key,node);

    }
    public void remove(Node node){
        Node pre=node.pre;
        Node next=node.next;
        pre.next=next;
        next.pre=pre;
    }
    public int removeLast(){
        Node tail=dummy.pre;
        remove(tail);
        return tail.key;
    }
    public void addFirst(Node node){
        //通过哨兵结点快速找到头结点
        Node head=dummy.next;
        head.pre=node;
        node.next=head;
        dummy.next=node;
        node.pre=dummy;
    }
}
全部评论
算法题好恶心
点赞 回复 分享
发布于 01-11 17:20 陕西

相关推荐

2025-12-31 19:36
已编辑
哈尔滨工业大学(威海) C++
一面&nbsp;12.2340&nbsp;分钟,刚面完官网马上就通过了,手撕第二道题想半天想不出来,面试官给了提示马上写出来了。鹅的面试官非常和蔼,全程笑着面完的,面试之前非常焦虑紧张,对自己的项目不是很熟悉,面试内容没怎么问项目,都是八股和算法,体验很好。面试问到的内容:值传递和引用传递提到了右值,什么时候用右值Unordered_map&nbsp;和&nbsp;map&nbsp;的区别Auto&nbsp;用过吗,什么时候用,有什么风险多继承有什么问题,菱形继承怎么解决虚函数表的原理C++&nbsp;怎么新建线程两个线程操纵一个变量会怎么样栈和堆了解吗,有什么区别程序编译运行过程发生了什么Static&nbsp;的函数有了解吗Const&nbsp;和&nbsp;constexpr字符的子串、旋转升序数组找最小值(二分查找)反问环节:部门做什么、后续流程IEG&nbsp;给王者等游戏提供工具优化、给公司其他部门提供工具。二面流程和一面差不多,不用太担心。二面&nbsp;12.2970&nbsp;分钟,一面面试官说二面和一面差不多让我别太担心,结果完全不是,一上来就问底层原理,操作系统给我拷打懵了,感觉啥也不会,虽然面试官给我解释然后让我重新答一遍,可我真的想不出来。面试问到的内容:看到你这个奖项,美赛得了什么奖?ACM&nbsp;打过吗?Elf&nbsp;有了解吗?虚拟地址和物理地址如何转换?快表的缩写是什么?如果查找从内存中查找一个数据,查到以后放到多级缓存中,放到哪一级?Linux&nbsp;中命令行定位搜索文件中的某个字符串在哪个文件静态链接和动态链接有了解吗?如果在一个&nbsp;h&nbsp;文件中定义一个类,然后在&nbsp;B、C&nbsp;中写这个类,有影响吗?如何避免头文件的重复调用?汇编文件了解吗?如何把分配在栈和堆中?别说这么多就说代码怎么写有两个线程,要分配一块空间,不加锁怎么实现(原子变量可行,面试官问不用原子变量如何实现)如果有一个类,里面只有一个&nbsp;int,然后他的子类是一个八字节的&nbsp;long&nbsp;long,这两个地址是挨着的吗?不是的话中间是什么?类型转换有了解吗?如果要把一个&nbsp;long&nbsp;long&nbsp;值转换为地址赋给指针要用什么?cmake了解吗?makefile会写吗?手撕:单调栈,几天后气温升高感觉不止这些,还问了很多,每个问题都追问得很细,想不起来了。不过确实都不怎么会,寒假得好好沉淀一下原理。
查看26道真题和解析
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务