题目描述

✅ 1360. 日期之间隔几天

image-20260928224420663

题意分析

两个有效日期使用固定格式 YYYY-MM-DD,求它们之间相隔的天数。输入不保证先后顺序,同一天相隔零天,相邻日期相隔一天,不是把两端日期都计入的天数。

题目日期范围为 1971 年到 2100 年,可以选 1971 年初作为共同参考起点。日期天数受月份长度和闰年影响,不能直接把年月日作为整数相减。

解法:日期转天数序号

核心思路

[!blue]

如果把每个日期转换成同一时间线上的天数序号,两个序号的绝对差就是间隔。每个序号分成三部分:当前年之前的完整年份、当前月之前的完整月份,以及当前月已走到的日号。

从 1971 年开始,只累加小于当前年的年份,每年按它自己的闰年状态计为 365 或 366 天。然后在当前年中,只累加小于当前月的月份。平年月份表中二月为 28 天,若已经经过二月且当前年是闰年,再增加一天。

公历闰年条件是“能被四百整除,或者能被四整除但不能被一百整除”。整百年份需要进一步看四百整除条件,不能一律采用每四年一闰。

代码最后加当前日号,让参考起点当天的序号为一。也可以统一使用从零开始的序号,但两个日期必须采用同一规则;公共偏移相减后自动消失,不影响结果。最后取绝对值,消除输入先后关系。

解题步骤

  1. 按固定字符串位置解析年、月、日。
  2. 累加 1971 年到当前年前一年的天数。
  3. 累加当年当前月之前的完整月份,跨过二月时按闰年规则补一天。
  4. 加上当前日号,得到统一序号。
  5. 分别转换两个日期,返回序号差的绝对值。

代码实现

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. 一月有多少天 简单 每个月的天数与闰年规则决定日期累计,二月边界不能按固定天数处理。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/28982558
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!