马上封版了,之后就可以开始躺平了吗?
1、先读题
如何一句话让社畜想个三分钟。
2、审题
题目简洁到说不出话来,但是具体有什么重点呢?
细说几个:
- 时间字符串是24小时制的“HH:MM”。
- 要求返回的最小时间差以分钟数表示
- 2 <= timePoints.length <= 2 * 104
3、思路
实际上这题我做了两种做法,分别是简单朴素好想的暴力做法,和稍微动脑后的优化循环。
总之,在放出第二种解法前,先讲讲暴力做法吧。
毕竟是我的第一想法。
class Solution {
public int findMinDifference(List<String> timePoints) {
List<Integer> timeInts = new ArrayList<>();
for (String time : timePoints) {
int timeInt = this.timeToInt(time);
timeInts.add(timeInt);
//当时间小于12:00时,同时为他添加一个跨越24:00小时的版本
if (timeInt <= 720) {
timeInts.add(timeInt + 1440);
}
}
timeInts.sort(Integer::compareTo);
int min = Integer.MAX_VALUE;
for (int i = 0; i < timeInts.size() - 1; i++) {
min = Math.min(timeInts.get(i + 1) - timeInts.get(i), min);
//由于排序后,后一个数字一定比前一个大,故当差值为0,即有两个数相等时,无需再判断后续
if (min == 0) {
break;
}
}
return min;
}
private int timeToInt(String time) {
String[] t = time.split(":");
return Integer.parseInt(t[0]) * 60 + Integer.parseInt(t[1]);
}
}
思路很简单,毕竟题目的要求是返回分钟数的差。于是,首先我们将所有时间字符串转换为分钟数即可。
实现逻辑见timeToInt()
方法,没有什么好讲的。
之后我们就得到了一个整数列表,里面存放了每个时间的分钟数。
但是,不仅如此,由于诸如“23:59”分这种时间,可能会和“00:30”这种时间间得到最小时间差值。
所以,为了保证前一天较晚的时间和后一天较早的时间之间可以比较,我将小于“12:00”的时间,都添加了一个加24小时的副本在这个集合内。
for (String time : timePoints) {
int timeInt = this.timeToInt(time);
timeInts.add(timeInt);
//当时间小于12:00时,同时为他添加一个跨越24:00小时的版本
if (timeInt <= 720) {
timeInts.add(timeInt + 1440);
}
}
之后就是对这个列表进行排序,再两两比较时间差,取其差值了。
这个思路显然是可行的,但为什么我又做了第二种呢?
毕竟耗时排名实在是太难看了。
简单分析下时间复杂度就会发现,我经历了一次遍历和一次排序,时间复杂度来到了O(nLogn),而由于我还添加了额外的元素,所以实际的复杂度会更高。
然后此时,我才真正的开始思考题目的重点。
将所有时间换成分钟数的思路是保留的。
然后24小时制内总共只有1440分钟,但时间字符串列表长度却可达到2 * 104。
这就很明显了,只要列表长度超过1440,就必然会有重复的时间字符串,而一旦重复,这题的答案就必定是0。
于是,我决定用长度为1440的整数数组来存储所有出现的时间,而一旦发现有重复的时间,就立即返回0。
除此之外,假如没有重复,我们也可以在这个长度为1440的数组上遍历,这个时间复杂度无论如何都是O©。
4、来看第二版
class Solution {
public int findMinDifference(List<String> timePoints) {
//00:00~23:59,总共1440分钟
byte[] timeInts = new byte[1440];
int timeInt = 0;
for (String time : timePoints) {
timeInt = this.timeToInt(time);
//当前分钟已经存在时,即有两个相同的时间时,直接返回0
if (timeInts[timeInt] != 0) {
return 0;
}
//设置当前时间为1
timeInts[timeInt] = 1;
}
//时间差的最大值也不可能达到1440
int min = 1440;
//此时timeInt指向了原列表的最后一个时间
int next = timeInt + 1;
int diff = 1;
//扫了一圈,回到最后一个时间时
while (next != timeInt) {
//扫到时间末尾时,回到开头
if (next == 1440) {
next = 0;
continue;
}
if (timeInts[next] == 0) {
//当前时间不存在时,继续查找
diff++;
} else {
//遇到存在的时间时,计算差值
min = Math.min(min, diff);
//重新计数
diff = 1;
}
next++;
}
min = Math.min(min, diff);
return min;
}
private int timeToInt(String time) {
String[] t = time.split(":");
return Integer.parseInt(t[0]) * 60 + Integer.parseInt(t[1]);
}
}
于是第二版就是这样了。
5、提交
无论是耗时排名还是内存消耗排名都好上不少。
6、咀嚼
经历了原列表的遍历和我们定长数组的遍历,时间复杂度为O(N + C), C=1440。
空间复杂度为O(C )。
7、学习大牛
其实,在第二种解法做完后不久,我就发现问题了。
由于分钟数仅有1440个,所以我完全可以根据输入的字符串列表长度是否超过1440来判断,这样的话甚至连循环都省去了。
而能进到我后续的逻辑的情况,肯定是n<=1440
的情况,此时让时间复杂度仍然为O( C ) 的话反而增加了耗时。
所以,来看看大牛们的做法吧。
实际上,这次官解的做法就很好了。
这种快速判段的方法的官方名称是鸽巢原理。
而第二种解法便是在第一种之上,加上了鸽巢原理的优化。
8、总结
总的来说,今天的中等题做得并不如意。
虽然不知道这种原理的名称,但我还是可以应用这种思想。但后续的判断逻辑就做得一般般了。