面试最常考的算法---LRU和LFU

12 篇文章 0 订阅
2 篇文章 0 订阅

面试最常考的算法—LRU和LFU

本题来自leetcode。这个方法是我自己写的,思路十分清晰。

1.LRU题目

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

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

  • 实现思路

LRU淘汰缓存机制,是通过时间顺序去淘汰的,查看相应的次序。常用的在前边的,不常用的在后边,当容量不够就删除后用的。所以用链表实现,当中间某一个key又被使用的时候需要掉换在最前面。更好的去删除,和修改。
使用双链表和hash表来实现。题目很简单,使用LinkedHashMap是java的常用类中提供的LUR算法。使用类很简单但是肯定不是面试官想要的。leetcode官方都这么说的

实现本题的两种操作,需要用到一个哈希表和一个双向链表。在面试中,面试官一般会期望读者能够自己实现一个简单的双向链表,而不是使用语言自带的、封装好的数据结构。在 Python 语言中,有一种结合了哈希表与双向链表的数据结构 OrderedDict,只需要短短的几行代码就可完成本题。在 Java 语言中,同样有类似的数据结构 LinkedHashMap。这些做法都不会符合面试官的要求,而不多做任何阐述。

class LRUCache {
    // 存放键 和结点 
    HashMap<Integer,Node> map = new HashMap<>();
    // 链表来实现缓存的先后顺序进行增删
    DoubleList list = new DoubleList();
    // 定义容量,进行初始化
    int cap ;
    public LRUCache(int capacity) {
        this.cap = capacity ;
    }
    
    public int get(int key) {
        //  查询map中如果有没有这个键就返回-1
        if(!map.containsKey(key)){
            return -1 ;
        }
        // 当存在时,一定要将其放在首位,所以从map中拿到节点,在链表中删除,然后新加
        Node node = map.get(key);
        list.remove(node);

        list.addLast(node);

        return node.value;
    }
    
    public void put(int key, int value) {

        // 先将节点新建
        Node node = new Node(key,value);
       // 查看节点的key是否存在,当存在的时候说明链表中已经有这个值了所以直接删除,在新增的时候就自动放在首位了,
       if(map.containsKey(key)){
          
           list.remove(map.get(key));

       }

       if(cap == list.size()){
           // 当内存溢出的时候要移除首位元素。但是不要忘了在map中也要移除。
           Node  first = list.removeFirst();
           map.remove(first.key);
       }
        // 
        list.addLast(node);

        // 当键存在就更新,不存在就新加
        map.put(key,node);
       
    }

    // 双向链表节点的类
    class Node{
        int key , value ;
        Node pre ,next ;
        public Node(int key ,int value){
            this.key = key;
            this.value = value;
        }
    }
    // 双向链表的实现
    class DoubleList{

        int size ;
        Node head ,tail;

        public DoubleList(){
            head = new Node(0,0);
            tail = new Node(0,0);
            this.size = 0 ;
            this.head.next = tail ;
            this.tail.pre = head ;
        }

        public void addLast(Node x){
            x.pre = tail.pre;
            x.next =tail;
            tail.pre.next = x ;
            tail.pre = x ; 
            size++ ;
        }

        public void remove(Node x){
            x.next.pre = x.pre ;
            x.pre.next = x.next;
            size--;
        }
        public Node removeFirst(){
            Node first = head.next;

            if(first == tail){
                return null ;
            }

            remove(first);

            return first;
        }

        public int size(){
            return size;
        }

    }
}

2. LFU题目

实现 LFUCache 类:
LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象
int get(int key) - 如果键存在于缓存中,则获取键的值,否则返回 -1。
void put(int key, int value) - 如果键已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量时,则应该在插入新项之前,使最不经常使用的项无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近最久未使用 的键。
注意「项的使用次数」就是自插入该项以来对其调用 get 和 put 函数的次数之和。使用次数会在对应项被移除后置为 0 。
为了确定最不常使用的键,可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。
当一个键首次插入到缓存中时,它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 get 或 put 操作,使用计数器的值将会递增。

  • 实现思路

LFU是利用使用的次数的多少来衡量石否删除。而LRU是通过使用的次序也就是时间顺序。
所以要实现LFU,首先得有一个表一个存储数据的表HashMap<Integer,Integer> keyToVal
其次就是得有一个表用来计算次数 HashMap<Integer,Integer> keyToFreq
当内存满的时候,需要删除相应的次数较少的key,所以必须有一个minFaareqKey来记录。
其次是为了好找最低次数避免遍历,所以需要一个次数对应key的一个表HashMap<Integer,LinkedHashSet<Integer>

class LFUCache {

    // key 到 val的映射
    HashMap<Integer,Integer> keyVal;
    // key 到 freq 的映射
    HashMap<Integer,Integer> keyToFreq;
    // freq到key的映射
    HashMap<Integer,LinkedHashSet<Integer>> ferqToKey;

    int minFreq;

    int cap ;
    // 初始化
    public LFUCache(int capacity) {
        keyVal = new HashMap<>();
        keyToFreq = new HashMap<>();
        ferqToKey = new HashMap<>();
        minFreq = 0;
        cap = capacity;
    }
    
    public int get(int key) {
        // 判断是否存在
        if(!keyVal.containsKey(key)){
            return -1;
        }
        // 存在的时候,将次数和相应的表自增
        increaseFreq(key);

        // 返回相应的数值
        return keyVal.get(key);

    }
    
    public void put(int key, int value) {
            
        if(this.cap <= 0){
            return ;
        }
        // 在填充的时候查看是否key已经存在,如果存在,则更新数值keyToVal表,次数增加
        if(keyVal.containsKey(key)){
            keyVal.put(key,value);

            increaseFreq(key);
            return;
        }
        // 如果不存在,在添加的时候判断一下看容器是否满了    
        if(cap == keyVal.size()){
            // 满了就删除次数少的key
            removeMinFreqKey();
        }
        // 如果没满就添加,并且将次数置为1,
        keyVal.put(key,value);

        keyToFreq.put(key,1);
        // 判断1次对应的key 有没有值就是null,没有就需要新建容器再添加。LinkedHashSet<>()底层使用hash表可以用通过key ,很快定位删除
        ferqToKey.putIfAbsent(1,new LinkedHashSet<>());

        ferqToKey.get(1).add(key);
        // 每次新增就意味者最小次数就又置为1 了
        this.minFreq = 1;
    }
    // 次数+1时所需要更新的表
    public void increaseFreq(int key){
        // 现得到现在的key对应的次数
        int freq = keyToFreq.get(key);
        // 更新keyToFreq表,将次数+1
        keyToFreq.put(key,freq+1);
        // freqToKey表中将之前freq次数所在的集合的键删除,在freq+1所对应的集合把key新增,在新增的时候看集合是否存在,不存在就先创建。
        ferqToKey.get(freq).remove(key);

        ferqToKey.putIfAbsent(freq+1,new LinkedHashSet<>());

        ferqToKey.get(freq+1).add(key);

        // 当原来的集合删除键之后应该判断一下,是否为空,为空就删除相应的次数所在的集合。
        if(ferqToKey.get(freq).isEmpty()){
            ferqToKey.remove(freq);
            // 当删除的集合是最小次数的集合时,就将最小次数+1
            if(freq == minFreq ){
                this.minFreq++;
            }
        }
    }

    public void removeMinFreqKey(){

        LinkedHashSet<Integer> list = ferqToKey.get(minFreq);

        int deleteKey = list.iterator().next();

        list.remove(deleteKey);

        if(list.isEmpty()){
            ferqToKey.remove(this.minFreq);
        }

        keyVal.remove(deleteKey);

        keyToFreq.remove(deleteKey);

    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值