Java 100道经典机试笔试题(09)——附可运行代码

导语

每篇将有两道经典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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值