最小时间差 图解题解
这道题到底在问什么
- 输入
- timePoints=["23:59","00:00"]
- 输出
- 1 (跨午夜只差 1 分钟)
- 输入
- timePoints=["00:00","23:59","00:00"]
- 输出
- 0 (有两个相同时间)
最优解:为什么这么做
一句话答案: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)。
▶ 动画逐步走查(共 21 步)——想跟着动画一帧帧对照就展开
- 3记住这套「换算分钟 → 排序 → 相邻求差 → 再补环形那一对」,下面每一帧都在套它。
- 4把第 0 个时间 23:50 折算成分钟:23 小时是 1380 分钟,再加 50 分,得 1430。灰色是还没换算的,绿色是已经换好的。
- 5把第 1 个时间 00:10 折算成分钟:0 小时是 0 分钟,再加 10 分,得 10。灰色是还没换算的,绿色是已经换好的。
- 6把第 2 个时间 12:00 折算成分钟:12 小时是 720 分钟,再加 0 分,得 720。灰色是还没换算的,绿色是已经换好的。
- 7把第 3 个时间 06:30 折算成分钟:6 小时是 360 分钟,再加 30 分,得 390。灰色是还没换算的,绿色是已经换好的。
- 8把第 4 个时间 18:45 折算成分钟:18 小时是 1080 分钟,再加 45 分,得 1125。灰色是还没换算的,绿色是已经换好的。
- 9五个时间点都换成了分钟:1430、10、720、390、1125。现在它们还是输入的乱序,下一步排序。
- 10从小到大排好序:10、390、720、1125、1430,对应时间是 00:10、06:30、12:00、18:45、23:50。排序后,差最小的两个时间一定是挨着的,这是这套解法的根。
- 11开始扫相邻对。两个指针指住排序后挨着的一对,从第 0、1 个起,一路往右滑。best 用来记一路见过的最小差,现在还空着。
- 12看排序后挨着的第 0、1 个:00:10(10 分)和 06:30(390 分)。它俩相邻,差就是 390 减 10,下一帧算出来。
- 13这一对相差 380 分钟。比之前的最小还小,best 刷新成 380。
- 14看排序后挨着的第 1、2 个:06:30(390 分)和 12:00(720 分)。它俩相邻,差就是 720 减 390,下一帧算出来。
- 15这一对相差 330 分钟。比之前的最小还小,best 刷新成 330。
- 16看排序后挨着的第 2、3 个:12:00(720 分)和 18:45(1125 分)。它俩相邻,差就是 1125 减 720,下一帧算出来。
- 17这一对相差 405 分钟。没有目前的 best=330 小,best 不变。
- 18看排序后挨着的第 3、4 个:18:45(1125 分)和 23:50(1430 分)。它俩相邻,差就是 1430 减 1125,下一帧算出来。
- 19这一对相差 305 分钟。比之前的最小还小,best 刷新成 305。
- 20相邻对扫完了,但还漏了一对:排在最后的 23:50 和排在最前的 00:10。它俩看着隔了大半天,可时间是环形的,跨过午夜其实可能很近,必须补这一刀。
- 21跨午夜的差怎么算:从最晚的 1430 分走到午夜(第 1440 分),再走到最早的 10 分,合起来是 10 加 1440 减 1430,等于 20 分钟。也就是 23:50 到第二天 00:10 只隔 20 分钟。
- 22环形差 20 分钟,比相邻里最小的 305 还小,best 刷新成 20。这正是这道题最容易漏的一对。
- 23扫完所有相邻对加上环形那一对,最小的是绿色高亮的这一对 23:50 和 00:10,跨午夜只差 20 分钟。答案就是 20。
⚠️ 容易写错的地方
✗ 错:只比相邻、漏掉跨午夜那一对
✓ 对:补一个「最小值加 1440 减最大值」的环形差
23:50 与 00:10 跨午夜只差 20 分钟,不补就错
✗ 错:直接两两暴力比 O(n²)
✓ 对:排序后只看相邻即可
排序后最近的两个时间必然相邻,无需全比
✗ 错:忘了鸽巢特判
✓ 对:时间点超过 1440 个直接返回 0
一天只有 1440 分钟,超过必有重复,差为 0
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from 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 = right
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class 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))C++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <optional>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
class Solution {
public:
int findMinDifference(vector<string>& timePoints) {
if (timePoints.size() > 1440) {
return 0;
}
int n = timePoints.size();
vector<int> nums(n + 1);
for (int i = 0; i < n; ++i) {
int hours = stoi(timePoints[i].substr(0, 2));
int minutes = stoi(timePoints[i].substr(3, 2));
nums[i] = hours * 60 + minutes;
}
sort(nums.begin(), nums.begin() + n);
nums[n] = nums[0] + 1440;
int ans = INT_MAX;
for (int i = 1; i <= n; ++i) {
ans = min(ans, nums[i] - nums[i - 1]);
}
return ans;
}
};Java
import java.util.*;
class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(){} TreeNode(int val){this.val=val;} TreeNode(int val, TreeNode left, TreeNode right){this.val=val;this.left=left;this.right=right;} }
class ListNode { int val; ListNode next; ListNode(){} ListNode(int val){this.val=val;} ListNode(int val, ListNode next){this.val=val;this.next=next;} }
class Solution {
public int findMinDifference(List<String> timePoints) {
if (timePoints.size() > 1440) {
return 0;
}
int n = timePoints.size();
int[] nums = new int[n + 1];
for (int i = 0; i < n; ++i) {
String[] t = timePoints.get(i).split(":");
nums[i] = Integer.parseInt(t[0]) * 60 + Integer.parseInt(t[1]);
}
Arrays.sort(nums, 0, n);
nums[n] = nums[0] + 1440;
int ans = 1 << 30;
for (int i = 1; i <= n; ++i) {
ans = Math.min(ans, nums[i] - nums[i - 1]);
}
return ans;
}
}复杂度
时间
O(n log n)
瓶颈在排序;换算和扫相邻都是 O(n)
空间
O(n)
存 n 个分钟数(含一个哨兵)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 最小时间差 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
能不能不排序、做到 O(n)?+
能。一天只有 1440 个可能的分钟值,开一个长度 1440 的桶,把每个时刻标记进去,同一个格子被标两次就说明有重复、直接返 0;再从头扫一遍桶,记下相邻两个被标记位置的最小间隔,最后照样补上首尾的环形差。时间 O(n + 1440),比排序更快,是这题的进阶写法。一般规模下排序版代码最短、最不容易写错,当默认解直接交出去也足够稳妥。
为什么时刻超过 1440 个就能直接返 0?+
一天总共只有 1440 个不同的 "HH:MM"。东西比坑多必有一坑装俩,时刻超过 1440 个时,必然有两个完全一样,它们的差是 0,不用再算。这一步顺带还省掉了大数据量下的一次排序,是个既保正确又提速的特判。
排序后为什么只比相邻的就够,不会漏掉更小的差?+
因为一列从小到大排好的数,差最小的两个一定是挨着的。设想差最小的两个中间还夹着第三个数,那第三个数离它们各自都更近,差只会比原来更小,跟「这两个差最小」矛盾。所以非相邻的对不可能比相邻对更小,全比一遍是白费。唯一的例外是首尾那一对——它俩在排序后被甩到两头,可时间成环,跨过午夜其实可能很近,得单独补一个环形差。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 最小时间差 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。