双向链表+哈希表实现O(1)

 146. LRU 缓存

请你设计并实现一个满足  LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

插入和查询使用哈希表即可以达到O(1)的时间复杂度,但是因为内存有限,我们要使用LRU算法更新缓存,所以我们一定要使用一个数据结构来记录”最近使用“这个数据,然后根据这个数据结构来选择键值对删除。

期望在 O(1)复杂度内调整某个节点在序列中的位置,很自然想到双向链表。

为了使用双向链表,我们考虑双向链表,结点我们自己创建的Node类,同时在哈希表内,我们使用Node类作为value,双向链表头部记录最近使用的结点,尾部记录最久没有使用的结点,并且在每次使用某个结点后将该结点移动到头部来,然后每次如果要溢出就清除尾部的最久没用的节点即可。

class LRUCache {
    //链表节点,记录左右和键值对
    class Node {
        int key,value;
        Node prev,succ;
        Node(){
            this.key=-1;
            this.value=-1;
        }
        Node(int key,int value){
            this.key=key;
            this.value=value;
        }
    }
    //容量初始化
    int n;
    //链表头尾节点
    Node head, tail;
    Map<Integer, Node> map;
    //初始化
    public LRUCache(int capacity) {
        n=capacity;
        map=new HashMap<>();
        head=new Node();
        tail=new Node();
        head.succ=tail;
        tail.prev=head;
    }
    
    public int get(int key) {
        if(map.containsKey(key)){
            Node node=map.get(key);
            refresh(node);
            return node.value;
        }
        return -1;
    }
    //放入数据,需要更新链表
    public void put(int key, int value) {
        Node node=null;
        if(map.containsKey(key)){
            node=map.get(key);
            node.value=value;
        }else{
            if(map.size()==n){
                Node toDel=tail.prev;
                delete(toDel);
                map.remove(toDel.key);
            }
            node=new Node(key,value);
            map.put(key, node);
        }
        refresh(node);
    }
 
    // refresh 操作主要是将最新的节点移动到双链表头部
    // 1. 先将当前节点从双向链表中删除(如果该节点本身存在于双向链表中的话)
    // 2. 将当前节点添加到双向链表头部
    void refresh(Node node) {
        delete(node);
        node.succ=head.succ;
        node.prev=head;
        head.succ.prev=node;
        head.succ=node;
    }
 
    // delete 操作:将当前节点从双向链表中移除
    // 由于我们预先建立 head 和 tail 两位哨兵,因此如果 node.l 不为空,则代表了 node 本身存在于双向链表(不是新节点)
    void delete(Node node) {
        if(node.succ!=null){
            node.prev.succ=node.succ;
            node.succ.prev=node.prev;
        }
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

如果不要求O(1)的话,这题就使用哈希表就可以了,但是哈希表只满足插入和更新O(1)复杂度,想要找到最大/最小值复杂度就达到O(n)了,因此要考虑别的方法

class AllOne {
    HashMap<String,Integer>map;
    public AllOne() {
        map=new HashMap<>();
    }
    
    public void inc(String key) {
        map.put(key, map.getOrDefault(key, 0)+1);
    }
    
    public void dec(String key) {
        int cnt=map.get(key);
        if(cnt==1)map.remove(key);
        else map.put(key,cnt-1);
    }
    
    public String getMaxKey() {
        String ans="";
        int max=0;
        for(String s:map.keySet()){
            if(map.get(s)>max){
                max=map.get(s);
                ans=s;
            }
        }
        return ans;
    }
    
    public String getMinKey() {
        String ans="";
        int min=Integer.MAX_VALUE;
        for(String s:map.keySet()){
            if(map.get(s)<min){
                min=map.get(s);
                ans=s;
            }
        }
        return ans;
    }
}

也就是类似LRU的方法,区别是结点并不是键值对,而是根据出现次数来分类,并且将出现次数相同的字符串放在一起

class AllOne {
    //节点记录所有出现次数相同的字符串,和他们的出现次数
    class Node {
        int cnt;
        Set<String> set = new HashSet<>();
        Node prev, succ;
        Node(int cnt) {
            this.cnt = cnt;
        }
    }
    
    Node head, tail;
    //map记录字符串对应节点
    Map<String, Node> map;
    //初始化
    public AllOne() {
        head = new Node(-1); 
        tail = new Node(-1);
        map = new HashMap<>();
        head.succ = tail; 
        tail.prev = head;
    }
    
    //如果一个节点已经没有数据,那么清空
    //防止影响查找
    void clear(Node node) {
        if (node.set.size() == 0) {
            node.prev.succ = node.succ;
            node.succ.prev = node.prev;
        }
    }
    
    public void inc(String key) {
        //已有节点
        if (map.containsKey(key)) {
            Node node = map.get(key);
            //增加也要将该字符从集合中移除,因为位置要调整了
            node.set.remove(key);
            int cnt = node.cnt;
            //寻找cnt+1的位置
            Node next = null;
            //如果已有,直接插入
            if (node.succ.cnt == cnt + 1) {
                next = node.succ;
            } else {
                //如果没有,就要新建
                next = new Node(cnt + 1);
                next.succ = node.succ;
                next.prev = node;
                node.succ.prev = next;
                node.succ = next;
            }
            //加入集合
            next.set.add(key);
            //加入哈希表
            map.put(key, next);
            //如果该节点只有这一个字符串,直接删除即可
            clear(node);
        } else {
            //没有还要新建节点
            Node node = null;
            //寻找合适的插入位置,记住新节点的cnt==1
            if (head.succ.cnt == 1) {
                //已有出现次数为1的
                node = head.succ;
            } else {
                //新建点一定是在head之后
                node = new Node(1);
                node.succ = head.succ;
                node.prev = head;
                head.succ.prev = node;
                head.succ = node;
            }
            //直接将字符串加入到集合里,并更新哈希表
            node.set.add(key);
            //注意如果两个字符串出现次数相同,那么映射到同一个节点
            map.put(key, node);
        }
    }
    
    public void dec(String key) {
        Node node = map.get(key);
        //必然从当前位置移除,找新家
        node.set.remove(key);
        int cnt = node.cnt;
        //如果该字符串只出现一次,那么直接移除该键值对
        if (cnt == 1) {
            map.remove(key);
        } else {
            //否则要找新家
            //理论上是前一个
            Node prev = null;
            //前一个出现次数正好是n-1,直接插到前面即可
            if (node.prev.cnt == cnt - 1) {
                prev = node.prev;
            } else {
                //如果前一个出现次数不是cnt-1,要新建一个节点插在前一个和这个之间
                prev = new Node(cnt - 1);
                prev.succ = node;
                prev.prev = node.prev;
                node.prev.succ = prev;
                node.prev = prev;
            }
            //加入set集合
            prev.set.add(key);
            //加入哈希表
            map.put(key, prev);
        }
        clear(node);
    }
    
    public String getMaxKey() {
        //最后的出现次数最多
        Node node = tail.prev;
        for (String str : node.set) return str;
        return "";
    }
    
    public String getMinKey() {
        //最前面的出现次数最少
        Node node = head.succ;
        for (String str : node.set) return str;
        return "";
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值