ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

LeetCode日期计算:高效算法与优化实践

LeetCode日期计算:高效算法与优化实践

1. 问题背景与需求解析

1360题是LeetCode上一道关于日期计算的经典题目,要求计算两个给定日期之间的天数差。这类问题在实际开发中经常遇到,比如计算会员有效期、统计用户活跃天数等场景。题目看似简单,但隐藏着几个关键难点:

  1. 不同月份的天数不一致(特别是2月的闰年问题)
  2. 日期跨越多年时需要正确处理闰年
  3. 边界条件处理(同一天、相邻日期等)

我在多次提交后最终实现了耗时100ms的解决方案(超过100%的提交),下面分享具体实现思路和优化技巧。

2. 日期计算的核心算法

2.1 日期序列化方案

最直接的思路是将日期转换为从某个固定起点(如公元1年1月1日)开始的总天数,然后做差值计算。这里的关键在于如何高效准确地计算这个"日期序数"。

def days_since_epoch(year, month, day): total = 0 # 计算年份贡献 for y in range(1, year): total += 366 if is_leap(y) else 365 # 计算当年月份贡献 for m in range(1, month): total += days_in_month(year, m) # 加上当月天数 total += day return total

2.2 闰年判断优化

常规的闰年判断规则:

  1. 能被4整除但不能被100整除,或者
  2. 能被400整除

可以简化为一行高效判断:

def is_leap(year): return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)

2.3 月份天数预处理

将每月天数预先存储为数组,注意处理闰年2月:

def days_in_month(year, month): if month == 2: return 29 if is_leap(year) else 28 if month in [4,6,9,11]: return 30 return 31

3. 性能优化实践

3.1 避免重复计算

原始方案中对每个年份都单独计算闰年,实际上可以数学推导出给定年份范围内的闰年数量:

def leap_years_before(year): return year // 4 - year // 100 + year // 400

3.2 日期序数计算公式优化

结合闰年数量计算,可以推导出O(1)复杂度的序数计算公式:

def date_to_ordinal(year, month, day): # 计算年份贡献 ord = year * 365 + leap_years_before(year) # 月份贡献 month_days = [0,31,28,31,30,31,30,31,31,30,31,30,31] if is_leap(year): month_days[2] = 29 ord += sum(month_days[:month]) # 当月天数 ord += day - 1 return ord

3.3 边界条件处理

特别注意几种边界情况:

  1. 同年同月同日
  2. 日期1晚于日期2
  3. 跨年日期计算

最终差值计算应取绝对值:

def days_between(date1, date2): return abs(date_to_ordinal(*date1) - date_to_ordinal(*date2))

4. 完整实现与测试用例

4.1 Python最终实现

def is_leap(year): return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) def date_to_ordinal(year, month, day): month_days = [0,31,28,31,30,31,30,31,31,30,31,30,31] if is_leap(year): month_days[2] = 29 ord = (year-1)*365 + (year-1)//4 - (year-1)//100 + (year-1)//400 ord += sum(month_days[:month]) ord += day - 1 return ord class Solution: def daysBetweenDates(self, date1: str, date2: str) -> int: y1,m1,d1 = map(int, date1.split('-')) y2,m2,d2 = map(int, date2.split('-')) return abs(date_to_ordinal(y1,m1,d1) - date_to_ordinal(y2,m2,d2))

4.2 关键测试用例

test_cases = [ ("2019-06-29", "2019-06-30", 1), # 相邻日期 ("2020-01-15", "2019-12-31", 15), # 跨年 ("1971-06-29", "2010-09-23", 14331), # 大跨度 ("2100-09-22", "1991-03-12", 40006), # 包含世纪闰年 ("2000-02-28", "2000-03-01", 2) # 闰年2月 ]

5. 经验总结与进阶思考

5.1 实际开发中的注意事项

  1. 时区问题:生产环境中必须考虑时区差异
  2. 历法变更:1582年10月的格里高利历改革导致10天缺失
  3. 性能考量:批量计算时应预计算序数映射表

5.2 其他日期处理方案对比

  1. Python标准库datetime:
from datetime import datetime def days_between(date1, date2): d1 = datetime.strptime(date1, "%Y-%m-%d") d2 = datetime.strptime(date2, "%Y-%m-%d") return abs((d2 - d1).days)

虽然代码简洁,但实际测试发现比我们的优化方案慢2-3倍。

  1. 第三方库pendulum: 提供更丰富的日期操作功能,但依赖外部包。

5.3 算法竞赛中的扩展应用

类似的日期处理技巧还可用于:

  • 计算某日是星期几(Zeller公式)
  • 节假日计算
  • 日历生成器实现

我在实际编码中发现,将日期转换为序数的思路可以统一处理许多时间计算问题。对于需要频繁计算日期差的场景,建议预先计算并缓存序数值。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进