华为OD机试 2024D卷题库疯狂收录中,刷题点这里
专栏导读
本专栏收录于《华为OD机试(JAVA)真题(D卷+C卷+A卷+B卷)》。
刷的越多,抽中的概率越大,每一题都有详细的答题思路、详细的代码注释、样例测试,发现新题目,随时更新,全天CSDN在线答疑。
一、题目描述
请设计一个文件缓存系统,该文件缓存系统可以指定缓存的最大值(单位为字节)。文件缓存系统有两种操作:存储文件(put)和读取文件(get)
操作命令为put fileName fileSize或者get fileName
存储文件是把文件放入文件缓存系统中;读取文件是从文件缓存系统中访问已存在的文件,如果文件不存在,则不作任何操作。
当缓存空间不足以存放新的文件时,根据规则删除文件,直到剩余空间满足新的文件大小为止,再存放新文件。
具体的删除规则为:文件访问过后,会更新文件的最近访问时间和总的访问次数,当缓存不够时,按照第一优先顺序为访问次数从少到多,第二顺序为时间从老到新的方式来删除文件。
二、输入描述
第一行为缓存最大值m(整数,取值范围为0 < m <= 52428800)
第二行为文件操作序列个数n(0 <= n <= 300000)
从第三行起为文件操作序列,每个序列单独一行
文件操作定义为"op fileName fileSize"
fileName是文件名,fileSize是文件大小
三、输出描述
输出当前文件缓存中的文件名列表,文件名用英文逗号分隔,按字典顺序排序
如:a,c
如果文件缓存中没有文件,则输出NONE
补充说明
- 如果新文件的文件名和文件缓存中已有的文件名相同,则不会放在缓存中
- 新的文件第一次存入到文件缓存中时,文件的总访问次数不会变化,文件的最近访问时间会更新到最新时间
- 每次文件访问后,总访问次数加1,最近访问时间更新到最新时间
- 任何两个文件的最近访问时间不会重复
- 文件名不会为空,均为小写字母,最大长度为10
- 缓存空间不足时,不能存放新文件
- 每个文件大小都是大于0的整数
1、输入
50
6
put a 10
put b 20
get a
get a
get b
put c 30
2、输出
a,c
四、解题思路
本题要求实现一个文件缓存系统,支持存储文件 (put) 和读取文件 (get) 操作。缓存的容量是有限的,当缓存空间不足时需要根据一定的规则删除文件。具体的删除规则为:
- 优先删除访问次数最少的文件。
- 如果访问次数相同,优先删除最近最少访问的文件。
为了解决这个问题,我们可以使用两个数据结构:
- HashMap:用于快速存取文件的节点信息。
- 双向链表:用于管理文件的访问顺序和频次。
具体步骤如下:
- 数据结构设计:
- Node 类:表示文件的节点,包含文件名、文件大小、访问次数、前后节点的引用。
- DoublyLinkedList 类:双向链表,用于管理文件节点,支持在链表尾部添加节点和删除指定节点。
- LFUCache 类:文件缓存系统,包含一个 keyMap(存储文件名与节点的映射)和一个 freqMap(存储访问次数与链表的映射)。
- 操作实现:
- put 方法:存储文件。如果缓存空间不足,则根据规则删除文件后再存储新文件。
- get 方法:读取文件并更新其访问次数。
- incNodeFreq 方法:更新节点的访问次数,并移动节点到新的频次链表。
- 具体步骤:
- 初始化:读取缓存最大容量和操作序列数量,创建 LFUCache 对象。
- 处理操作序列:逐条处理 put 和 get 操作,更新缓存。
- 输出结果:输出缓存中的文件名列表,按字典顺序排序。如果缓存为空,输出 “NONE”。
五、Java算法源码
public class Test01 {
// 定义双向链表节点
static class Node {
String fileName; // 文件名
int fileSize; // 文件大小
int accessCount; // 访问次数
Node prev; // 前一个节点
Node next; // 后一个节点
public Node(String fileName, int fileSize, int accessCount) {
this.fileName = fileName;
this.fileSize = fileSize;
this.accessCount = accessCount;
this.prev = null;
this.next = null;
}
}
// 定义双向链表
static class DoublyLinkedList {
int size; // 链表节点数
Node head; // 链表头节点
Node tail; // 链表尾节点
// 在链表尾部添加节点
public void addLast(Node node) {
if (this.size == 0) {
this.head = node;
this.tail = node;
} else {
this.tail.next = node;
node.prev = this.tail;
this.tail = node;
}
this.size++;
}
// 删除指定节点
public void remove(Node node) {
if (this.size == 0) return;
if (this.size == 1) {
this.head = null;
this.tail = null;
} else if (node == this.head) {
this.head = this.head.next;
this.head.prev = null;
} else if (node == this.tail) {
this.tail = this.tail.prev;
this.tail.next = null;
} else {
node.prev.next = node.next;
node.next.prev = node.prev;
}
this.size--;
}
}
static class LFUCache {
HashMap<String, Node> keyMap; // 存储文件名与节点的映射
HashMap<Integer, DoublyLinkedList> freqMap; // 存储访问次数与链表的映射
int capacity; // 文件系统总容量
int minFreq; // 最少访问次数
public LFUCache(int capacity) {
this.capacity = capacity;
this.minFreq = 0;
this.keyMap = new HashMap<>();
this.freqMap = new HashMap<>();
}
// 获取文件并更新其访问次数
public void get(String fileName) {
if (!this.keyMap.containsKey(fileName)) return;
Node node = this.keyMap.get(fileName);
incNodeFreq(node); // 更新访问次数
}
// 存储文件
public void put(String fileName, int fileSize) {
if (this.keyMap.containsKey(fileName)) return;
while (this.capacity < fileSize) {
if (this.minFreq == 0) return;
DoublyLinkedList minFreqList = this.freqMap.get(this.minFreq);
Node removeNode = minFreqList.head;
this.capacity += removeNode.fileSize;
minFreqList.remove(removeNode);
this.keyMap.remove(removeNode.fileName);
if (minFreqList.size == 0) {
this.freqMap.remove(this.minFreq);
if (this.freqMap.size() > 0) {
this.minFreq = this.freqMap.keySet().stream().min((a, b) -> a - b).get();
} else {
this.minFreq = 0;
}
}
}
this.capacity -= fileSize;
this.minFreq = 1;
Node node = new Node(fileName, fileSize, this.minFreq);
this.freqMap.putIfAbsent(this.minFreq, new DoublyLinkedList());
this.freqMap.get(this.minFreq).addLast(node);
this.keyMap.put(fileName, node);
}
// 更新节点的访问次数
public void incNodeFreq(Node node) {
DoublyLinkedList list = this.freqMap.get(node.accessCount);
list.remove(node);
if (list.size == 0) {
this.freqMap.remove(node.accessCount);
if (node.accessCount == this.minFreq) {
this.minFreq++;
}
}
node.accessCount++;
this.freqMap.putIfAbsent(node.accessCount, new DoublyLinkedList());
this.freqMap.get(node.accessCount).addLast(node);
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取缓存最大容量
int maxCapacity = Integer.parseInt(scanner.nextLine());
LFUCache lfuCache = new LFUCache(maxCapacity);
// 读取操作序列的数量
int operationCount = Integer.parseInt(scanner.nextLine());
for (int i = 0; i < operationCount; i++) {
String[] operation = scanner.nextLine().split(" ");
String op = operation[0];
String fileName = operation[1];
if ("put".equals(op)) {
int fileSize = Integer.parseInt(operation[2]);
lfuCache.put(fileName, fileSize);
} else {
lfuCache.get(fileName);
}
}
// 输出结果
if (lfuCache.capacity == maxCapacity) {
System.out.println("NONE");
} else {
StringJoiner sj = new StringJoiner(",");
lfuCache.keyMap.keySet().stream().sorted().forEach(sj::add);
System.out.println(sj);
}
}
}
六、效果展示
1、输入
50
7
put a 10
put b 20
get a
get a
get b
get b
put c 30
2、输出
b,c
3、说明
🏆下一篇:华为OD机试 - 简易内存池 - 逻辑分析(Java 2024 C卷 200分)
🏆本文收录于,华为OD机试(JAVA)真题(D卷+C卷+A卷+B卷)
刷的越多,抽中的概率越大,每一题都有详细的答题思路、详细的代码注释、样例测试,发现新题目,随时更新,全天CSDN在线答疑。