程序员代码面试指南第二版 156.设计LRU缓存结构

welcome to my blog

程序员代码面试指南第二版 156.设计LRU缓存结构

题目描述
设计LRU缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能
set(key, value):将记录(key, value)插入该结构
get(key):返回key对应的value值

[要求]
set和get方法的时间复杂度为O(1)
某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的。
当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。

输入描述:
第一行两个个整数N, K,表示操作数量以及缓存结构大小
接下来N行,第一行一个整数opt表示操作类型。
若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1

输出描述:
对于每个操作2,输出一个答案

示例1

输入
6 3
1 1 1
1 2 2
1 3 2
2 1
1 4 4
2 2

输出
1
-1

说明
第一次操作后:最常使用的记录为("1", 1)
第二次操作后:最常使用的记录为("2", 2),("1", 1)变为最不常用的
第三次操作后:最常使用的记录为("3", 2),("1", 1)还是最不常用的
第四次操作后:最常用的记录为("1", 1),("2", 2)变为最不常用的
第五次操作后:大小超过了3,所以移除此时最不常使用的记录("2", 2),加入记录("4", 4),并且为最常使用的记录,然后("3", 2)变为最不常使用的记录
第一次做; 基础数据结构太重要!
/*
set和get方法的时间复杂度为O(1)
链表的set时间复杂度为O(1)
哈希表的get时间复杂度为O(1)
将链表和哈希表结合

为了在O(1)时间删除节点和挪动节点(更新状态), 要使用双向链表
*/

import java.util.Scanner;
import java.util.Map;
import java.util.HashMap;

public class Main{
    public static void main(String[] args){
        //定义双指针节点类, 为实现双向链表
        class Node<V>{
            int val;
            Node last;
            Node next;
            Node(int val){
                this.val = val;
            }
        }
        //定义双向链表类; 实现三个功能:1)增加节点 2)删除头节点 3)将节点挪到链表末尾(有细节)
        class DoubleLinkedList<V>{
            Node<V> head;
            Node<V> tail;
            //1)增加节点; addNode后面要加泛型吗? 还是说类名加了泛型就可以了, 这里不用加?
            //细节: 传给addNode的是现成的node, 千万不能在addNode内部创建节点
            public void addNode(Node<V> node){
                if(this.head==null){
                    this.head = node;
                    this.tail = node;
                }
                else{
                    this.tail.next = node;
                    node.last = this.tail;
                    this.tail = node;
                }
            }
            //2)删除头结点
            public Node<V> removeHead(){
                //如果链表为空
                if(this.head == null)
                    return null;
                Node<V> curr = this.head;
                //如果链表只有一个节点
                if(this.head == this.tail){
                    this.head = null;
                    this.tail = null;
                }
                else{
                    this.head.next.last = null;
                    this.head = this.head.next;
                    //细节: 将原头结点的next指向null, 这样原头结点和链表就彻底没有联系了
                    curr.next = null;
                }
                return curr;
            }
            //3)将节点挪动到链表结尾; 挪动某个节点到最后说明链表中一定有这个节点, 也说明链表一定不为空! 细节: 按照node是否是tail分类讨论
            public void moveNodeToTail(Node<V> node){
                //如果node是tail, 不用执行操作
                if(node==this.tail)
                    return;
                //如果node是head
                if(node==this.head){
                    this.head.next.last = null;
                    this.head = this.head.next;
                }
                //如果node是链表中间的某个节点
                else{
                    node.last.next = node.next;
                    node.next.last = node.last;
                }
                this.tail.next = node;
                node.last = this.tail;
                node.next = null;
                this.tail = node;
            }
        }
        //定义LRUCache类; 需要哈希表记录key对应的val; 需要哈希表记录val对应的key; 需要实现两个功能:1)set(key,val) 2)get(key) 其中,2)可能需要删除LRUCache中最不常用的记录(双向链表的头结点);
        //set(key, val)和get(key)伴随着将记录改成最常使用的状态的操作
        class LRUCache{
            private Map<Integer, Node<Integer>> keyNodeMap;
            private Map<Node<Integer>, Integer> nodeKeyMap;
            private int capacity;
            //DoubleLinkedList的泛型是节点中V的类型,所以是V, 不是Node<V>
            private DoubleLinkedList<Integer> nodeList;
            LRUCache(int capacity){
                keyNodeMap = new HashMap<>();
                nodeKeyMap = new HashMap<>();
                this.capacity = capacity;
                nodeList = new DoubleLinkedList<Integer>();
            }
            //1)set(key, val)
            public void put(int key, int val){
                //如果LRUCache中有这个key
                if(keyNodeMap.containsKey(key)){
                    Node<Integer> curr = keyNodeMap.get(key);
                    curr.val = val;
                    //将当前记录改成最常使用的状态
                    nodeList.moveNodeToTail(curr);
                }
                //如果LRUCache中没有这个key
                else{
                    //如果缓存已满, 那么得先删掉最不常使用的记录
                    if(keyNodeMap.size() == this.capacity){
                        Node<Integer> head = nodeList.removeHead();
                        //更新哈希表
                        Integer keyTBD = nodeKeyMap.get(head);
                        nodeKeyMap.remove(head);
                        keyNodeMap.remove(keyTBD);
                    }
                    Node<Integer> node = new Node<>(val);
                    keyNodeMap.put(key, node);
                    nodeKeyMap.put(node, key);
                    nodeList.addNode(node);
                }
            }
            //2)get(key)
            public int get(Integer key){
                //如果LRUCache中有这个key
                if(keyNodeMap.containsKey(key)){
                    //将当前记录改成最常使用的状态
                    Node<Integer> curr = keyNodeMap.get(key);
                    nodeList.moveNodeToTail(curr);
                    return curr.val;
                }
                //如果LRUCahe中没有这个key
                else{
                    return -1;
                }
            }
        }
        
        Scanner sc = new Scanner(System.in);
        String s = sc.nextLine();
        int n = Integer.parseInt(s.split(" ")[0]);
        int k = Integer.parseInt(s.split(" ")[1]);
        LRUCache cache = new LRUCache(k);
        for(int i=0; i<n; i++){
            s = sc.nextLine();
            int opt = Integer.parseInt(s.split(" ")[0]);
            switch(opt){
                case 1:
                    int key = Integer.parseInt(s.split(" ")[1]);
                    int val = Integer.parseInt(s.split(" ")[2]);
                    cache.put(key, val);
                    break;
                case 2:
                    int key2 = Integer.parseInt(s.split(" ")[1]);
                    int res = cache.get(key2);
                    System.out.println(res);
                    break;
            }
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值