题目描述
思路解析
一句话答案:LeetCode 539 最小时间差:把每个 HH:MM 换成分钟排序,最近的两个必相邻,再补一个跨午夜的环形差取最小,时间 O(n log n)、空间 O(n)。
一堆 HH:MM 时间点,最小差是几分钟
给一组 "HH:MM" 格式的时间点,求任意两个时刻相差最少多少分钟。这里要留意一天是首尾相接的:23:59 和 00:00 看着隔了一整天,其实只差 1 分钟。题面例子 ["23:59","00:00"] 返回 1;["00:00","23:59","00:00"] 里有两个 00:00,返回 0。
两两都算一次差,一万个时刻要比五千万次
把每两个时刻都算一次差、取最小,能直接得到答案。可 n 个时刻有 n(n-1)/2 对,一万个就是约五千万次比较,O(n²) 在时刻一多时就撑不住。而且这么比既没用上「时间成环」这个条件,也没占到「时刻可以排序」的便宜。
化成分钟排好序,最近的两个一定挨着
先把每个 "HH:MM" 换成从零点算起的分钟数:小时乘 60 加上分钟,值落在 0 到 1439。换完从小到大排成一列,一个能省掉大半比较的性质就冒出来了:一列有序的数,差最小的两个一定相邻。若中间还夹着第三个数,那个数离两边都更近,差只会更小,自相矛盾。所以排序后只比每一对相邻的,最小差必在其中。
但这样还漏了一对。排序把最早和最晚的时刻甩到了两头,它俩在数轴上离得最远,可时间是环的:从最晚的时刻跨过午夜走到第二天最早的时刻,这段差是「最小值 加 1440 减 最大值」。这一对不落在任何相邻位置上,得单独补一刀,和所有相邻差放一起取最小才是答案。
返回全局最小,开算前还有个鸽巢特判
要比的就两类:排序后每对相邻的差,加上那一个环形差,全部取最小就是结果。参考代码用个小技巧把两类并进同一次循环——排序后往末尾补一个「最小值加 1440」当哨兵,最后一对相邻差恰好就是环形差,一行 min 全收了。
开算前还能拦一道:时刻数超过 1440 就直接返 0。一天只有 1440 个不同的 "HH:MM",东西比坑多必有一坑装俩,超过 1440 个时刻里必然撞出两个相同的,最小差就是 0,连排序都省了。
["23:59","00:00"] 走一遍,环形差怎么救回答案
两个时刻换成分钟:23:59 是 23×60+59=1439,00:00 是 0。排序后是 [0, 1439]。相邻只有一对,1439 减 0 等于 1439,看着差了大半天。再补环形差:最小值 0 加 1440 减最大值 1439,等于 1。两者取最小,得 1。要是只比相邻、漏了环形那一对,就会错报成 1439。换成 ["00:00","23:59","00:00"],两个 00:00 换出来都是 0,相邻差直接是 0,结果 0。
跨午夜那一对,忘补就整道错
环形那一对最容易蒸发——只扫相邻、忘了给最小值加一天,跨午夜明明很近的两个时刻会被算成隔了大半天。别退回两两暴力,排序后最近的两个已经保证相邻,全比一遍纯属白算一个平方级。还有那个鸽巢特判,时刻超过 1440 个时不加它结果照样对,但会白排一次大序。
复杂度上,换算和扫相邻都是线性的,整体落在 O(n log n),那一次从小到大的排序是唯一超出线性的步骤;额外存 n 个分钟数外加一个哨兵,空间 O(n)。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记住这套「换算分钟 → 排序 → 相邻求差 → 再补环形那一对」,下面每一帧都在套它。
把第 0 个时间 23:50 折算成分钟:23 小时是 1380 分钟,再加 50 分,得 1430。灰色是还没换算的,绿色是已经换好的。
把第 1 个时间 00:10 折算成分钟:0 小时是 0 分钟,再加 10 分,得 10。灰色是还没换算的,绿色是已经换好的。
把第 2 个时间 12:00 折算成分钟:12 小时是 720 分钟,再加 0 分,得 720。灰色是还没换算的,绿色是已经换好的。
把第 3 个时间 06:30 折算成分钟:6 小时是 360 分钟,再加 30 分,得 390。灰色是还没换算的,绿色是已经换好的。
把第 4 个时间 18:45 折算成分钟:18 小时是 1080 分钟,再加 45 分,得 1125。灰色是还没换算的,绿色是已经换好的。
五个时间点都换成了分钟:1430、10、720、390、1125。现在它们还是输入的乱序,下一步排序。
从小到大排好序:10、390、720、1125、1430,对应时间是 00:10、06:30、12:00、18:45、23:50。排序后,差最小的两个时间一定是挨着的,这是这套解法的根。
开始扫相邻对。两个指针指住排序后挨着的一对,从第 0、1 个起,一路往右滑。best 用来记一路见过的最小差,现在还空着。
看排序后挨着的第 0、1 个:00:10(10 分)和 06:30(390 分)。它俩相邻,差就是 390 减 10,下一帧算出来。
这一对相差 380 分钟。比之前的最小还小,best 刷新成 380。
看排序后挨着的第 1、2 个:06:30(390 分)和 12:00(720 分)。它俩相邻,差就是 720 减 390,下一帧算出来。
这一对相差 330 分钟。比之前的最小还小,best 刷新成 330。
看排序后挨着的第 2、3 个:12:00(720 分)和 18:45(1125 分)。它俩相邻,差就是 1125 减 720,下一帧算出来。
这一对相差 405 分钟。没有目前的 best=330 小,best 不变。
看排序后挨着的第 3、4 个:18:45(1125 分)和 23:50(1430 分)。它俩相邻,差就是 1430 减 1125,下一帧算出来。
这一对相差 305 分钟。比之前的最小还小,best 刷新成 305。
相邻对扫完了,但还漏了一对:排在最后的 23:50 和排在最前的 00:10。它俩看着隔了大半天,可时间是环形的,跨过午夜其实可能很近,必须补这一刀。
跨午夜的差怎么算:从最晚的 1430 分走到午夜(第 1440 分),再走到最早的 10 分,合起来是 10 加 1440 减 1430,等于 20 分钟。也就是 23:50 到第二天 00:10 只隔 20 分钟。
环形差 20 分钟,比相邻里最小的 305 还小,best 刷新成 20。这正是这道题最容易漏的一对。
扫完所有相邻对加上环形那一对,最小的是绿色高亮的这一对 23:50 和 00:10,跨午夜只差 20 分钟。答案就是 20。
边界先想清:跨午夜、有重复、超过 1440 个。
两个高频追问:计数桶 O(n) 优化、鸽巢特判的来历。
参考代码
from __future__ import annotationsfrom typing import *from collections import *from functools import *from itertools import *from math import *from heapq import *from bisect import *class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = rightclass ListNode: def __init__(self, val=0, next=None): self.val = val self.next = nextclass Solution: def findMinDifference(self, timePoints: List[str]) -> int: if len(timePoints) > 1440: return 0 nums = sorted(int(x[:2]) * 60 + int(x[3:]) for x in timePoints) nums.append(nums[0] + 1440) return min(b - a for a, b in pairwise(nums))复杂度
- 时间:O(n log n),瓶颈在排序;换算和扫相邻都是 O(n)
- 空间:O(n),存 n 个分钟数(含一个哨兵)
易错点
面试追问把动画讲成自己的话
追问能不能不排序、用计数桶做到 O(n)?
追问为什么时间点超过 1440 个就能直接返回 0?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
任务调度器
LeetCode 621 · 中等 · 沿着 贪心套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题