LeetCode Top 100 Liked Questions 146. LRU Cache (Java版; Medium)

welcome to my blog

LeetCode Top 100 Liked Questions 146. LRU Cache (Java版; Medium)

题目描述
Design and implement a data structure for Least Recently Used (LRU,最近最少使用) cache. It should support the following operations: get and put.

get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate 
the least recently used item before inserting a new item.

The cache is initialized with a positive capacity.

Follow up:
Could you do both operations in O(1) time complexity?

Example:

LRUCache cache = new LRUCache( 2 /* capacity */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // returns 1
cache.put(3, 3);    // evicts key 2
cache.get(2);       // returns -1 (not found)
cache.put(4, 4);    // evicts key 1
cache.get(1);       // returns -1 (not found)
cache.get(3);       // returns 3
cache.get(4);       // returns 4
class LRUCache {
    DoubleLinkedList dll;
    int capacity;
    HashMap<Integer, Node> map;
    public LRUCache(int capacity) {
        dll = new DoubleLinkedList();
        this.capacity = capacity;
        map = new HashMap<>();
    }
    
    public int get(int key) {
        if(!map.containsKey(key)){
            return -1;
        }
        dll.moveToTail(map.get(key));
        return map.get(key).val;
    }
    
    public void put(int key, int value) {
        if(map.containsKey(key)){
            map.get(key).val = value;
            dll.moveToTail(map.get(key));
            return;
        }
        if(map.size()==capacity){
            Node node = dll.removeHead();
            map.remove(node.key);
        }
        Node node = new Node(key, value);
        dll.addToTail(node);
        map.put(key, node);
    }

    private class Node{
        int key;
        int val;
        Node pre;
        Node next;
        Node(int key, int val){
            this.key = key;
            this.val = val;
        }
    }

    //双向链表; 提供三个方法:(1)将元素添加到队尾 (2)将元素移动到队尾 (3)删除队首元素
    private class DoubleLinkedList{
        Node head;
        Node tail;
        private void addToTail(Node node){
            if(head==null){
                head = node;
                tail = node;
            }else{
                tail.next = node;
                node.pre = tail;
                tail = node;
            }
        }

        private void moveToTail(Node node){
            //如果链表中只有一个元素
            if(node==tail){
                return;
            }
            //如果链表中至少有两个元素
            if(node==head){
                head = head.next;
                head.pre = null;
                node.next = null;
                //
                tail.next = node;
                node.pre = tail;
                //
                tail = node;
            }else{
                Node left = node.pre;
                left.next = node.next;
                node.next.pre = left;
                //
                tail.next = node;
                node.pre = tail;
                //
                tail = node;
            }
        }

        private Node removeHead(){
            Node node=head;
            if(head==tail){
                head=null;
                tail=null;
            }else{
                head = head.next;
                head.pre = null;
            }
            return node;
        }
    }
}
第一次做; 这道题让我体会到基础数据结构太重要了, 复杂的数据结构是由基础数据结构构成的; 核心: 双向链表+哈希表, 双向链表的头结点是最不常用的数据, 双向链表的尾结点是最常用的数据; 要自定义节点类, 链表类, LRUCache类; 其中, 链表类需要实现三个功能:1)增加节点, 2)删除头结点, 3)将某个节点移动到链表结尾(有细节); LRUCache类需要实现三个功能:1)set, 2)get, 3)缓存满了之后删除最不常用的节点; 定义节点类和链表类的时候不妨使用泛型, 方便扩展到其它数据类型上
/*
双向链表+哈希表
考查的是:根据具体功能设计相应的数据结构的能力; 所以基础的数据结构太重要了! 务必夯实基础
*/

import java.util.HashMap;

//定义双向节点类; 此处为了加深理解, 使用泛型
class Node<V>{
    public V value;
    public Node<V> last;
    public Node<V> next;
    public Node(V value){
        this.value = value;
    }
}
//定义双向链表类; 需要实现三个功能:1)添加节点 2)将节点挪到最后 3)删除头节点
class DoubleLinkedList<V>{
    private Node<V> head;
    private Node<V> tail;
    public DoubleLinkedList(){
        this.head = null;
        this.tail = null;
    }
    //1)添加节点
    public void addNode(Node<V> newNode){
        //input check
        if(newNode == null)
            return;
        //链表为空
        if(this.head==null){
            this.head = newNode;
            this.tail = newNode;
        }
        //链表不为为空; 将新节点添加到队列末尾
        else{
            this.tail.next = newNode;
            newNode.last = this.tail;
            this.tail = newNode;
        }
    }
    //2)将节点挪到最后
    public void moveNodeToTail(Node<V> node){
        //如果node本来就在末尾, 就不用调整了; 这里包含两个情况:1)链表只有一个节点, 不用调整 2)链表有多个节点, 但是node在末尾, 不用调整
        if(node == this.tail)
            return;
        //如果node是头结点; 先改变头结点, 之后再将node挪到最后
        if(this.head == node){
            this.head = node.next;
            this.head.last = null;
        }
        //如果node不是头结点; 先将node左右两侧的节点连接, 之后再将node挪到最后
        else{
            node.last.next = node.next;
            node.next.last = node.last;
        }
        //将node挪到最后; 主要node的last和next指针都要变动
        this.tail.next = node;
        node.last = this.tail;
        node.next = null;
        this.tail = node;
    }
    //3)删除头节点
    public Node<V> removeHead(){
        //如果链表为空
        if(this.head==null)
            return null;
        Node<V> curr = this.head;
        //如果链表只有一个节点; 删除头结点后, head和tail指针变为null
        if(this.head == this.tail){
            this.head = null;
            this.tail = null;
        }
        //如果链表有多个节点; 删除头结点后, head指针变了, tial指针可能改变(如果有两个节点,tail.last会变; 如果有两个以上的节点, tail不会变)
        else{
            //删除头节点需要改变头结点的last和next指针; 这一点和挪动节点很像, 都是需要改变两个指针
            this.head = this.head.next;
            this.head.last = null;
            curr.next = null;
        }
        return curr;
    }
    
}
class LRUCache {
    private HashMap<Integer, Node<Integer>> keyNodeMap;
    private HashMap<Node<Integer>, Integer> nodeKeyMap;
    //双向链表
    private DoubleLinkedList<Integer> nodeList;
    private int capacity;
    public LRUCache(int capacity) {
        //input check
        if(capacity < 1)
            throw new RuntimeException("capacity is not valid!!!!!!!!!");
        this.capacity = capacity;
        this.keyNodeMap = new HashMap<Integer, Node<Integer>>();
        this.nodeKeyMap = new HashMap<Node<Integer>, Integer>();
        this.nodeList = new DoubleLinkedList<Integer>();
    }
    
    public int get(int key) {
        //链表不包含key, 返回-1
        if(!keyNodeMap.containsKey(key))
            return -1;
        //链表中包含key, 返回对应的value
        else{
            Node<Integer> res = keyNodeMap.get(key);
            //将该节点更新成: 最常使用的, 也就是把该节点挪到链表末尾
            nodeList.moveNodeToTail(res);
            return res.value;
        }
    }
    
    public void put(int key, int value) {
        //如果链表中包含key
        if(keyNodeMap.containsKey(key)){
            Node curr = keyNodeMap.get(key);
            curr.value = value;
            //将该节点更新成: 最常使用的
            nodeList.moveNodeToTail(curr);
        }
        //如果链表中不包含key; 需要创建一个新的节点
        else{
            //先检查缓存是否已满; 满的话, 需要删除头结点
            if(keyNodeMap.size() == this.capacity){
                removeMostUnusedCache();
            }
            //确保缓存有空间后再加入新的记录
            Node<Integer> node = new Node<>(value);
            //更新哈希表中的记录
            this.keyNodeMap.put(key, node);
            this.nodeKeyMap.put(node, key);
            //把节点添加到链表中
            nodeList.addNode(node);
        }
    }
    public void removeMostUnusedCache(){
        Node<Integer> curr = this.nodeList.removeHead();
        //更新哈希表
        int key = nodeKeyMap.get(curr);
        this.keyNodeMap.remove(key);
        keyNodeMap.remove(curr);
    }
}

/**
 * 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);
 */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值