导语
每篇将有两道经典Java机试题,每道题后面均为大家附上代码,每一道题目力求:
- 能够在JDK11环境下编译
- 在Eclipse JavaIDE中运行通过
- 思路易想易懂易学
- 重点代码有注释
第017题 时间更正(难度:★☆☆☆☆)
题目描述:
小南瓜有一个电子时钟用于显示时间,显示的格式为HH:MM:SS,HH,MM,SS分别表示时,分,秒。其中时的范围为[‘00’,‘01’…‘23’],分的范围为[‘00’,‘01’…‘59’],秒的范围为[‘00’,‘01’…‘59’]。
但是有一天小南瓜发现钟表似乎坏了,显示了一个不可能存在的时间“98:23:00”,小南瓜希望改变最少的数字,使得电子时钟显示的时间为一个真实存在的时间,譬如“98:23:00”通过修改第一个’9’为’1’,即可成为一个真实存在的时间“18:23:00”。修改的方法可能有很多,小南瓜想知道,在满足改变最少的数字的前提下,符合条件的字典序最小的时间是多少。其中字典序比较为用“HHMMSS”的6位字符串进行比较。
每个输入数据包含多个测试点。每个测试点后有一个空行。 第一行为测试点的个数T(T<=100)。 每个测试点包含1行,为一个字符串”HH:MM:SS”,表示钟表显示的时间。
对于每个测试点,输出一行。如果钟表显示的时间为真实存在的时间,则不做改动输出该时间,否则输出一个新的”HH:MM:SS”,表示修改最少的数字情况下,字典序最小的真实存在的时间。
输入示例:
2 19:90:23 23:59:59
输出示例:
19:00:23 23:59:59
思路
这个题目看懂了之后,很简单。题目要求首先要保证输出结果都是正确的时间,所以时分秒都要判断,时大于等于24,就要改,分和秒大于等于60就要改;
其次,题目要求,改为正确时间的诸多方案中,要输出改动时间最小的一个,时间最小按照HHMMSS比较,很显然,把不符合的时间最高位改成0,就是所有方案中时间序最小的;
有很多解法,这里提供一种从理解上来说最容易的代码。(有更简洁的代码,比如直接使用正则,差不多几行就能搞定,可以自己试试)
代码
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Test {
private static final int TYPE_HH = 0;
private static final int TYPE_MM = 1;
private static final int TYPE_SS = 2;
public static void main(String[] args) {
List<String> mTimeList = new ArrayList<>();
Scanner scanner = new Scanner(System.in);
// 接受输入几行
int count = scanner.nextInt();
// 保存每行输入的时间
for (int i = 0; i < count; i++) {
mTimeList.add(scanner.next());
}
scanner.close();
for (int i = 0; i < mTimeList.size(); i++) {
// 把时间按照“:”分割
String[] hmsString = mTimeList.get(i).split(":");
// 检查时分秒的合法性
String[] resultTime = checkTime(hmsString);
// 更新该时间
mTimeList.set(i, resultTime[0] + ":" + resultTime[1] + ":" + resultTime[2]);
}
// 输出
for (String time : mTimeList) {
System.out.println(time);
}
}
// 检查HHMMSS哪些不是合法时间
public static String[] checkTime(String[] hmStrings) {
// 因为要按照HHMMSS的字典顺序最小,那么要从SS开始判断
for (int i = hmStrings.length - 1; i >= 0; i--) {
// 纠正时间
int s = updateTime(Integer.valueOf(hmStrings[i]), (i == 0) ? TYPE_HH : TYPE_SS);
hmStrings[i] = String.valueOf(s);
// 纠正的时间,如果小于10,则需要补一个0;
if (s < 10) {
hmStrings[i] = "0" + hmStrings[i];
}
}
return hmStrings;
}
public static int updateTime(int time, int type) {
switch (type) {
// 对于时,大于等于24的,就只保留个位
case TYPE_HH:
if (time >= 24) {
time = time % 10;
}
break;
// 对于分和秒,大于等于60的,就只保留个位
case TYPE_MM:
case TYPE_SS:
if (time >= 60) {
time = time % 10;
}
break;
}
return time;
}
}
运行结果
输入
100
00:00:00
99:99:99
28:19:97
26:55:45
19:39:91
40:68:10
16:05:84
20:49:86
16:66:08
20:36:98
16:69:77
31:41:98
27:37:79
17:05:24
18:38:93
06:28:01
34:28:15
22:23:72
18:17:91
18:56:00
11:73:93
09:37:37
08:35:41
04:04:62
10:31:23
04:87:05
04:63:57
01:93:17
03:69:79
29:19:21
28:21:99
01:27:79
39:40:00
23:42:04
19:03:59
40:38:43
21:58:22
31:04:64
20:25:08
04:48:91
00:63:46
14:56:98
35:98:84
09:72:06
04:46:41
14:59:47
22:39:69
08:60:93
26:09:39
23:80:06
17:16:16
12:09:06
00:58:29
25:35:46
01:71:50
26:68:94
32:35:29
30:70:15
25:76:51
26:49:26
40:55:51
40:93:08
36:08:32
10:86:98
31:25:80
07:20:06
23:73:02
15:27:50
27:96:47
17:46:86
29:82:06
13:40:24
34:02:83
36:78:91
11:88:85
24:93:89
35:84:69
17:61:16
00:73:82
35:53:16
37:77:02
39:64:11
10:59:35
40:26:74
02:23:71
05:97:90
00:33:43
37:15:99
33:20:70
32:48:12
28:29:43
07:65:96
02:26:15
03:60:21
16:92:42
29:03:71
09:06:75
04:26:32
37:67:13
34:80:42
输出
00:00:00
09:09:09
08:19:07
06:55:45
19:39:01
00:08:10
16:05:04
20:49:06
16:06:08
20:36:08
16:09:07
01:41:08
07:37:09
17:05:24
18:38:03
06:28:01
04:28:15
22:23:02
18:17:01
18:56:00
11:03:03
09:37:37
08:35:41
04:04:02
10:31:23
04:07:05
04:03:57
01:03:17
03:09:09
09:19:21
08:21:09
01:27:09
09:40:00
23:42:04
19:03:59
00:38:43
21:58:22
01:04:04
20:25:08
04:48:01
00:03:46
14:56:08
05:08:04
09:02:06
04:46:41
14:59:47
22:39:09
08:00:03
06:09:39
23:00:06
17:16:16
12:09:06
00:58:29
05:35:46
01:01:50
06:08:04
02:35:29
00:00:15
05:06:51
06:49:26
00:55:51
00:03:08
06:08:32
10:06:08
01:25:00
07:20:06
23:03:02
15:27:50
07:06:47
17:46:06
09:02:06
13:40:24
04:02:03
06:08:01
11:08:05
04:03:09
05:04:09
17:01:16
00:03:02
05:53:16
07:07:02
09:04:11
10:59:35
00:26:04
02:23:01
05:07:00
00:33:43
07:15:09
03:20:00
02:48:12
08:29:43
07:05:06
02:26:15
03:00:21
16:02:42
09:03:01
09:06:05
04:26:32
07:07:13
04:00:42
第018题 LFU缓存算法(难度:★★★☆☆)
题目描述:
一个缓存结构需要实现如下功能。
- set(key, value):将记录(key, value)插入该结构
- get(key):返回key对应的value值
但是缓存结构中最多放K条记录,如果新的第K+1条记录要加入,就需要根据策略删掉一条记录,然后才能把新记录加入。这个策略为:在缓存结构的K条记录中,哪一个key从进入缓存结构的时刻开始,被调用set或者get的次数最少,就删掉这个key的记录;
如果调用次数最少的key有多个,上次调用发生最早的key被删除
这就是LFU缓存替换算法。实现这个结构,K作为参数给出
[要求]
1、set和get方法的时间复杂度为O(1)
2、使用下面的预设代码,已经指定好类名、方法名、参数名,请勿修改或重新命名,直接返回值即可
public class Solution {
/**
* lru design
* @param operators int整型二维数组 the ops
* @param k int整型 the k
* @return int整型一维数组
*/
public int[] LFU (int[][] operators, int k) {
// write code here
}
}输入说明:
若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1
对于每个操作2,输出一个答案
1≤k≤n≤10^5 −2×10^9≤x,y≤2×10^9
输入示例:
[[1,1,1],[1,2,2],[1,3,2],[1,2,4],[1,3,5],[2,2],[1,4,4],[2,1]],3
输出示例:
[4,-1]
思路
首先解释一下这个用例的输出,为什么是[4,-1],而不是[4,4]
按照题目的意思,每个数组的第一个数代表操作方式,是set还是get,即是存缓存还是取缓存;
那么第一组[1,1,1],就是要存放缓存,key为1,value也为1,然后放进缓存里:[{1,1}];
第二组[1,2,2],存缓存,key为2,value为1;由于缓存容量输入的是3,所以没有超过缓存大小,所以可以继续放进缓存,此时缓存为:[{1,1}, {1,2}];
第三组[1,3,2],存缓存,key为3,value为1;由于缓存容量输入的是3,所以没有超过缓存大小,所以可以继续放进缓存,此时缓存为:[{1,1}, {1,2}, {3,2}];
到了第四组[1,2,4],存缓存,key为2,value为4,这个时候,缓存容量已经满了,必须要找一个缓存替换掉它,找谁呢;题目说,找调用次数最少(每调用set,get方法一次,都算是被调用了一次)的,但是现在,前三组被调用的次数都是1;题目又说了,当调用次数一样时,替换掉被调用最早的一个,那么到目前为止,三个缓存是按照顺序加入缓存的,所以显然,第一个加入的缓存,被调用的最早,所以这里被替换的缓存是{1,1},所以,替换后的缓存为:[{1,2},{3,2},{2,4}];
同理,到了第五组[1,3,5],存缓存,key为3,value为5,这个时候,缓存容量已经满了,需要替换缓存,按照题意,被替换掉的是{1,2},所以,这一轮的结果为[{3,2},{2,4},{3,5}];
而到了第六组[2,2],操作数为2,取缓存,且取key为2的缓存值,所以返回值为4;
第七组[1,4,4],再次存缓存,key为4,value为4,这个时候,缓存容量已经满了,需要替换缓存;由谁替换呢,这个时候{3,2}被调用了1次,{2,4}被调用了2次(一次存,一次取),{3,5}被调用了1次;而上次被调用最早的是{3,2},所以被替换的是{3,2},即此时缓存中为:[{2,4},{3,2},{4,4}];
到了第八组[2,1],操作数为2,要取缓存,key为1,但是目前缓存里没有key为1的,所以找不到,返回-1;
那么根据以上分析,我们解这道题的思路也就有了:
1、需要一个结构体Node,保存它的调用次数count,key值,value值;
2、需要一个列表List<Node>来保存缓存;
3、需要的逻辑有:存缓存、取缓存;存缓存涉及增加和替换;取缓存,直接取当前列表中最早存入缓存key相等的value值(因为可能存在key相同的,value不相同的缓存,这个时候应该是要取最早存入缓存的那一个与key相等的value,这一点,题目应该是漏说了,不然,没法确定取哪一个);
4、在需要的逻辑里,稍微比较困难的是:替换;列表天然带有顺序,所以我们可以利用这个顺序,来表示时间上的顺序,越在list前面的,表示被调用的越早;在替换时,首先给当前的列表排序,按照被调用次数count的大小排序,排在最前面的,自然是需要被替换的;那么这种方式能解决有多个count相同时,替换上次被调用时间最早的情况吗?可以的,只要在排序的时候,确保count相同的Node,之间的相对顺序不变即可,那么被替换的依旧时排序完成后,处在最前面的一个Node;
代码
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Solution {
/**
* lfu design
* @param operators int整型二维数组 ops
* @param k int整型 the k
* @return int整型一维数组
*/
public int[] LFU (int[][] operators, int k) {
LFU lfu = new LFU(k);
//存储get出来的值
List<Integer> resultIntegers = new ArrayList<>();
//依次将每组数取出
for (int [] operator : operators) {
//如果第一位的操作数是1,则执行的是set操作
if (operator[0] == 1) {
lfu.set(operator[1], operator[2]);
}
//如果第一位的操作数是2,则执行的是get操作
else if (operator[0] == 2) {
resultIntegers.add(lfu.get(operator[1]));
}
}
/*
* int [] res = new int[resultIntegers.size()]; for(int i = 0; i <
* resultIntegers.size(); i++) res[i] = resultIntegers.get(i); return res;
*/
//将结果列表借助流转换成int[]数组返回
return resultIntegers.stream().mapToInt(Integer::valueOf).toArray();
}
}
class LFU{
//使用列表存储缓存
private List<Node> nodeList = new ArrayList<>();
//缓存大小
private int LFUCount;
public LFU(int count) {
LFUCount = count;
}
public void set(int key, int value) {
//如果当前缓存容量小于给定的缓存大小,直接往里添加
if (nodeList.size() < LFUCount) {
addNode(key, value);
}
//否则就先按照使用次数count从小到大排序,再移除第一个
//很重要的一点,这样会保留当count相等时,按照时间顺序调用的关系不会乱
else {
//排序
Collections.sort(nodeList, new Comparator<Node>() {
@Override
public int compare(Node o1, Node o2) {
//升序
return o1.count - o2.count;
}
});
//所以第一个是需要被移除的
nodeList.remove(0);
//加在最后面,保证按时间顺序调用的关系不会乱
addNode(key, value);
}
}
public int get(int key) {
//按顺序查找第一个key与给定的key相等的value
for (Node node: nodeList) {
if (node.key == key) {
//被调用的次数加一
node.count++;
//返回value
return node.value;
}
}
//找不到返回-1
return -1;
}
//添加节点方法
private void addNode(int key, int value) {
Node node = new Node();
node.key = key;
node.value = value;
node.count++;
nodeList.add(node);
}
}
class Node {
//该缓存节点被使用的次数
public int count;
//key值
public int key;
//value值
public int value;
}
运行结果
由于题目只要求写关键函数,所以我补充了一个很简单的main函数验证的结果,可以提供给你们
main函数如下:
public class Demo {
public static void main(String[] args) {
int[][] operators = {{1,1,1},{1,2,2},{1,3,2},{1,2,4},{1,3,5},{2,2},{1,4,4},{2,1}};
int k = 3;
Solution solution = new Solution();
int [] result = solution.LFU(operators, k);
String resultString = "";
for (int i = 0; i < result.length; i++) {
if (i == 0) {
resultString = resultString + "[";
}
resultString = resultString + result[i];
resultString = resultString + ((i == result.length - 1) ? "]" : ",");
}
System.out.print(resultString);
}
}
以上是本次两道Java机试题
如有不足,欢迎批评指正
欢迎阅读上一篇:Java 100道典型机试笔试题(08)
欢迎阅读下一篇:Java 100道典型机试笔试题(10)
作者:小南瓜
日期:2021年5月23日16:28