Java每日一题——>剑指 Offer II 035. 最小时间差(三解,蛮力,排序,哈希)

Posted stormzhuo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java每日一题——>剑指 Offer II 035. 最小时间差(三解,蛮力,排序,哈希)相关的知识,希望对你有一定的参考价值。

题目

这是LeetCode上的 [035,最小时间差],难度为 [中等]

给定一个 24 小时制(小时:分钟 “HH:MM”)的时间列表,找出列表中任意两个时间的最小时间差并以分钟数表示。

示例 1:

输入:timePoints = ["23:59","00:00"]
输出:1

示例 2:

输入:timePoints = ["00:00","23:59","00:00"]
输出:0

提示:

  • 2 <= timePoints <= 2 * 104
  • timePoints[i] 格式为 "HH:MM"

题解1(蛮力法)

由于时间列表的时间是没有大小顺序的,若不对时间进行排序,则需要使用蛮力法。

蛮力法就是在遍历时间列表时,让当前时间与当前时间后面的每个时间都进行相减得到时间差,时间差最短的即为最小时间

特别地,当时间列表的长度超过1440(24小时有1440分钟),这说明时间列表中最少有两个相同的时间,相同的时间我们按当天的时间处理,则时间差为0即为最小时间差

同样的,在遍历时间列表时,如果发现当前时间与当前时间后面的某个时间相同,则我们认为最小时间差为0

特别地,如果时间列表的存在时间00:00,则有两种情况的可能,当天的00:00或第二天的00:00,而上面地算法,默认认为是当天的00:00

因此我们还需要把第二天的时间00:00也考虑进去,换句话说就是把时间列表的最早的时间加上1440(24小时)变成第二天的时间,然后减去时间列表的最晚的一个时间得到时间差

需要注意的时,时间列表的时间是没有大小顺序的,所有相减的时间差可能为负值,故需要求绝对值

代码实现

class Solution 
    public int findMinDifference(List<String> timePoints) 
       // 若时间列表大于1440,则至少存在两个相等的时间,可以认为时间差为0
        if (timePoints.size() > 1440) 
            return 0;
        
        // 记录最小时间差,初始为最大值
        int minDiff = 1439;
        // 遍历过程中的当前时间
        int currTime = -1;
        // 遍历过程中当前时间的后面所有时间
        int nextTime = -1;
        // 记录最小时间
        int first = 1439;
        // 记录最大时间
        int last = -1;
        // 遍历时间列表,遍历过程中让当前时间与当前时间后面的所有时间做时间差并记录最小的时间差
        for(int i = 0; i < timePoints.size(); i++) 
            String[] curr = timePoints.get(i).split(":");
            // 遍历过程的当前时间
            currTime = Integer.parseInt(curr[0]) * 60 + Integer.parseInt(curr[1]);
            // 当前时间后面的所有时间
            for (int j = i + 1; j < timePoints.size(); j++) 
                String[] next = timePoints.get(j).split(":");
                nextTime = Integer.parseInt(next[0]) * 60 + Integer.parseInt(next[1]);
                // 若当前时间与当前时间后面的某个时间相同,则时间差为0
                if (currTime == nextTime) 
                    return 0;
                
                minDiff = Math.min(Math.abs(nextTime - currTime), minDiff);
                first = Math.min(nextTime < currTime ? nextTime : currTime, first);
                last = Math.max(nextTime > currTime ? nextTime : currTime, last);
            
        
       /* 把最小的时间变成第二天的时间,然后减去最大的时间得到时间差
          若此时间差比最小时间时间差还小,则此时间差就是最小的时间差*/
        minDiff = Math.min(first + 1440 - last, minDiff);
        return minDiff;
    

复杂度分析

假设时间列表的长度为n

时间复杂度

由于需要遍历时间列表,遍历过程中的当前时间需要和当前时间后面的所有结点做时间差,故时间复杂度为O(n2)

空间复杂度

由于声明的变量都是固定的,因此空间复杂度为O(1)

题解2(排序)

如果对时间进行排序,那么在遍历时间列表时,由于时间是排好序的,因此我们只需要让两两相邻的时间做时间差即可

特别地,当时间列表的长度超过1440,这说明时间列表中最少有两个相同的时间,相同的时间我们按当天的时间处理,则时间差为0即为最小时间差

同样的,在遍历时间列表算出分钟数时,如果发现集合已经包含此分钟数时,则说明有两个时间相同,则我们认为最小时间差为0

特别地,如果时间列表的存在时间00:00,则有两种情况的可能,当天的00:00或第二天的00:00,而上面地算法,默认认为是当天的00:00

因此我们还需要把第二天的时间00:00也考虑进去,换句话说就是把时间列表的最早的时间加上1440(24小时)变成第二天的时间,然后减去时间列表的最晚的一个时间得到时间差

代码实现

class Solution 
    public int findMinDifference(List<String> timePoints) 
 		// 若时间列表大于1440,则至少存在两个相等的时间,可以认为时间差为0
        if (timePoints.size() > 1440) 
            return 0;
        
        // 动态数组,存放时间
        ArrayList<Integer> timeList = new ArrayList<>();
        // 遍历时间列表,算出时间的分钟数并添加的集合中
        for (String timePoint : timePoints) 
            String[] split = timePoint.split(":");
            int time = Integer.parseInt(split[0]) * 60 + Integer.parseInt(split[1]);
            // 判断时间列表的时间是否已包含在集合中,若已包含,说明时间相同,则认为时间差为0
            if(timeList.contains(time)) 
                return 0;
            
            timeList.add(time);
        
        // 调用集合工具类对集合进行排序
        Collections.sort(timeList);
        // 存在最小时间差,初始为最大值
        int minDiff = 1439;
        // 存放遍历集合过程中的当前时间的前一个时间
        int pre = -1;
        // 存放最小的时间,即集合第一个元素
        int first = 1439;
        // 存放最大的时间,即集合最后一个元素
        int last = -1;
        // 遍历集合
        for (int i = 0; i < timeList.size(); i++) 
            int curr = timeList.get(i);
            /* 遍历过程的当前时间减去当前时间的前一个时间算出时间差,并于最小时间差比较求最小值
            * 第一个循环时,当前时间没有前一个时间,故不执行*/
            if (pre >= 0) 
                minDiff = Math.min(curr - pre, minDiff);
            
            // 当前时间作为当前时间下一个时间的前一个时间
            pre = curr;
            // 求出最小时间
            first = Math.min(curr, first);
            // 求出最大时间
            last = Math.max(curr, last);
        
        // 最小时间加上1440(24小时)变成第二天的时间,再减去最大时间得到时间差
        minDiff = Math.min(first + 1440 - last, minDiff);
        return minDiff;
    

复杂度分析

假设时间列表的长度为n

时间复杂度

由于需要对时间进行排序,时间复杂度为O(nlogn),排序之后遍历集合,比较两两相邻的时间差,时间复杂度为O(n),故总的时间复杂度为O(nlogn) + O(n) = O(nlogn)

空间复杂度

由于当时间列表长度大于1440时(存在至少两个相同的时间),返回的时间差为0,所有集合的长度不会大于1440,是常数,故空间复杂度为O(1)

题解3(哈希表)

上题时间主要花在排序上,那有什么办法天然的让时间排序好呢?

一天有24小时,即1440小时,我们可以用一个容量为1440的布尔型数组来表示一天的时间,即数组下标为0的位置对应时间00:00,下标为1的位置对应时间00:01,以此类推,下标为1439的位置对应23:59。

那么在时间列表中的时间就可以对应数组的下标,即如果数组中的下标等于时间列表的时间,我们就让此下标的值为true,自然而然就把时间 排序好了

因此只需在遍历时间列表过程中,算出当前时间的分钟数,分钟数对应数组下标,让此下标的值为true

最后只需遍历数组,找到下标的值为true的两个时间即为两个相邻的时间,然后算出时间差,比较时间差即可算出最小时间差

特别地,当时间列表的长度超过1440,这说明时间列表中最少有两个相同的时间,相同的时间我们按当天的时间处理,则时间差为0即为最小时间差

同样的,在遍历时间列表算出分钟数时,如果发现此分钟数的下标的值已经为true,则说明有两个时间相同,则我们认为最小时间差为0

特别地,如果时间列表的存在时间00:00,则有两种情况的可能,当天的00:00或第二天的00:00,而上面数组的下标0对应的时间00:00,我们默认是当天的时间,因此我们还需要把第二天的时间00:00也考虑进去

换句话说就是把时间列表的第一个时间加上1440变成第二天的时间,然后减去时间列表的最后一个时间得到时间差

代码实现

class Solution 
    public int findMinDifference(List<String> timePoints) 
       // 若时间列表大于1440,则至少存在两个相等的时间,可以认为时间差为0
        if (timePoints.size() > 1440) 
            return 0;
        
        // 创建创建为1440的布尔数组存放一天的时间,模拟哈希表
        boolean[] minuteFlags = new boolean[1440];
        // 遍历时间列表,遍历过程中算出分钟数
        for (String timePoint : timePoints) 
            String[] split = timePoint.split(":");
            int minute = Integer.parseInt(split[0]) * 60 + Integer.parseInt(split[1]);
            // 判断分钟数(下标)的值是否为true,若为true,则说明有两个相同的时间,返回0
            if (minuteFlags[minute]) 
                return 0;
            
            // 令此分钟数(下标的值)为true
            minuteFlags[minute] = true;
        
        return helper(minuteFlags);
    

    private int helper(boolean[] minuteFlags) 
        // 记录最小时间差,初始为最大值
        int minDiff = minuteFlags.length -1;
        // 存放遍历数组过程中的当前时间的前一个时间
        int pre = -1;
        // 存放最小时间,即数组第一个下标为true的时间
        int first = minuteFlags.length - 1;
        // 存放最大时间,即数组最后一个下标为true的时间
        int last = -1;
        // 遍历数组
        for (int i = 0; i < minuteFlags.length; i++) 
            // 若下标值为true,则执行下面逻辑,否则执行下一此循环
            if (minuteFlags[i]) 
                // 由于第一次循环时,当前时间没有前一个时间,则pre小于0,不执行此逻辑
                if (pre >= 0) 
                    // 若当前时间等于当前时间的前一个时间,则两个时间相等,最小时间差为0
                    if (i == pre) 
                        return 0;
                    
                    // 当前时间减去当前时间的前一个时间
                    minDiff = Math.min(i - pre, minDiff);
                
                // 重置当前时间的前一个时间,让当前时间的作为当前时间的下一个时间的前一个时间
                pre = i;
                // 记录最小时间,数组第一个下标为true的时间
                first = Math.min(i, first);
                // 记录最大时间,数组最后一个下标为true的时间
                last = Math.max(i, last);
            
        
        // 数组的第一个下标为true的时间加上24小时变成第二天的时间,然后减去当天的最晚时间
        minDiff = Math.min(first + minuteFlags.length - last, minDiff);
        return minDiff;
    


复杂度分析

假设时间列表的长度为n

时间复杂度

需要遍历时间列表把时间添加到数组中,时间复杂度为O(n),最后遍历数组算出两两相邻的时间,数组长度为1440,是常数,时间复杂度为O(1),故总的时间复杂度为O(n)

空间复杂度

数组的长度是固定的,故空间复杂度为O(1)

以上是关于Java每日一题——>剑指 Offer II 035. 最小时间差(三解,蛮力,排序,哈希)的主要内容,如果未能解决你的问题,请参考以下文章

Java每日一题——>剑指 Offer II 035. 最小时间差(三解,蛮力,排序,哈希)

Java每日一题——> 剑指 Offer II 028. 展平多级双向链表

Java每日一题——>剑指 Offer II 027. 回文链表

Java每日一题——>剑指 Offer II 030. 插入删除和随机访问都是 O 的容器

Java每日一题——>剑指 Offer II 034. 外星语言是否排序

Java每日一题——>剑指 Offer II 034. 外星语言是否排序