LeetCode 1360. 日期之间隔几天
题目描述

题意分析
两个有效日期使用固定格式
YYYY-MM-DD,求它们之间相隔的天数。输入不保证先后顺序,同一天相隔零天,相邻日期相隔一天,不是把两端日期都计入的天数。题目日期范围为 1971 年到 2100 年,可以选 1971 年初作为共同参考起点。日期天数受月份长度和闰年影响,不能直接把年月日作为整数相减。
解法:日期转天数序号
核心思路
[!blue]
如果把每个日期转换成同一时间线上的天数序号,两个序号的绝对差就是间隔。每个序号分成三部分:当前年之前的完整年份、当前月之前的完整月份,以及当前月已走到的日号。
从 1971 年开始,只累加小于当前年的年份,每年按它自己的闰年状态计为 365 或 366 天。然后在当前年中,只累加小于当前月的月份。平年月份表中二月为 28 天,若已经经过二月且当前年是闰年,再增加一天。
公历闰年条件是“能被四百整除,或者能被四整除但不能被一百整除”。整百年份需要进一步看四百整除条件,不能一律采用每四年一闰。
代码最后加当前日号,让参考起点当天的序号为一。也可以统一使用从零开始的序号,但两个日期必须采用同一规则;公共偏移相减后自动消失,不影响结果。最后取绝对值,消除输入先后关系。
解题步骤
- 按固定字符串位置解析年、月、日。
- 累加 1971 年到当前年前一年的天数。
- 累加当年当前月之前的完整月份,跨过二月时按闰年规则补一天。
- 加上当前日号,得到统一序号。
- 分别转换两个日期,返回序号差的绝对值。
代码实现
class Solution {
public int daysBetweenDates(String date1, String date2) {
int days1 = countDays(date1);
int days2 = countDays(date2);
// 同一参考日起算的序号作绝对差,不依赖日期先后。
return Math.abs(days1 - days2);
}
private int countDays(String date) {
int year = Integer.parseInt(date.substring(0, 4));
int month = Integer.parseInt(date.substring(5, 7));
int day = Integer.parseInt(date.substring(8, 10));
int[] monthDays = {
31,
28,
31,
30,
31,
30,
31,
31,
30,
31,
30,
31
};
int answer = 0;
// 此前完整年份按各自的闰年状态累加。
for (int y = 1971; y < year; y++) {
if (isLeapYear(y)) {
answer += 366;
} else {
answer += 365;
}
}
for (int m = 1; m < month; m++) {
answer += monthDays[m - 1];
// 只有日期所在年为闰年且已经跨过二月,才补一天。
if (m == 2 && isLeapYear(year)) {
answer++;
}
}
return answer + day;
}
private boolean isLeapYear(int year) {
return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0);
}
}
func daysBetweenDates(date1 string, date2 string) int {
days1 := countDays(date1)
days2 := countDays(date2)
// 同一参考日起算的序号作绝对差,不依赖日期先后。
if days1 > days2 {
return days1 - days2
}
return days2 - days1
}
func countDays(date string) int {
year := atoi(date[:4])
month := atoi(date[5:7])
day := atoi(date[8:])
monthDays := []int{
31,
28,
31,
30,
31,
30,
31,
31,
30,
31,
30,
31,
}
answer := 0
// 此前完整年份按各自的闰年状态累加。
for y := 1971; y < year; y++ {
if isLeapYear(y) {
answer += 366
} else {
answer += 365
}
}
for m := 1; m < month; m++ {
answer += monthDays[m-1]
// 只有日期所在年为闰年且已经跨过二月,才补一天。
if m == 2 && isLeapYear(year) {
answer++
}
}
return answer + day
}
func isLeapYear(year int) bool {
return year%400 == 0 || (year%4 == 0 && year%100 != 0)
}
func atoi(s string) int {
num := 0
for i := 0; i < len(s); i++ {
num = num*10 + int(s[i]-'0')
}
return num
}
复杂度分析
- 时间复杂度:$O(Y+1)$,其中 $Y$ 为较晚日期与参考年的年份差,月份至多扫描十一项。本题年份范围固定,也可视为常数时间。
- 空间复杂度:$O(1)$,月份表长度固定为十二。
关键点总结
[!green]
- 不直接比较年月日差,而是转换成统一的日序号。
- 年、月循环只统计已经完整经过的部分,当前日号最后补入。
- 每个完整年份独立判断闰年,当年月份部分则只看日期所在年。
- 相减会抵消统一的编号偏移,绝对值保证结果与输入顺序无关。
易错点总结
[!yellow]
- 只判断四年一闰,会把可被一百整除、但不能被四百整除的年份误判为闰年。
- 当前年份或当前月份也作为完整部分累加,会重复计算尚未经过的日期。
- 一月、二月就无条件补闰日,会把尚未经过的二月末多算一天。
- 天数差不要再加一,同一天必须返回零。
- 两个日期必须使用相同参考起点和编号约定,且结果需取绝对值。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 1154. 一年中的第几天 | 简单 | 先把日期转换成年内或统一起点的累计天数,再求两个日期的差。 |
| 1118. 一月有多少天 | 简单 | 每个月的天数与闰年规则决定日期累计,二月边界不能按固定天数处理。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!