1. 日期类算法问题概述
日期和时间处理是编程面试和机试中的高频考点,这类问题看似简单,但涉及大量边界条件和细节处理。在实际开发中,日期计算更是业务系统的核心功能之一,比如电商平台的优惠券有效期计算、金融系统的利息结算、社交平台的纪念日提醒等场景都离不开精确的日期处理。
日期类问题的核心在于掌握三大基础能力:闰年判断规则、每月天数计算、日期合法性校验。在此基础上,可以衍生出日期加减、差值计算、星期计算等复杂问题。这类题目考察的不仅是编码能力,更是对边界条件的敏感度和细节处理能力。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心基础:闰年判断与每月天数
2.1 闰年判断规则详解
闰年规则看似简单,但容易记错。正确的判断逻辑是:
- 能被400整除的年份是闰年
- 能被4整除但不能被100整除的年份是闰年
- 其他情况都是平年
这个规则源于格里高利历(公历)的规定,目的是弥补人为历法与地球公转周期之间的误差。一个常见的误区是认为"能被4整除就是闰年",忽略了"不能被100整除"的例外情况。例如1900年能被4整除,但同时也能被100整除,所以不是闰年;而2000年能被400整除,是闰年。
c复制// 标准闰年判断函数实现
int is_leap(int year) {
// 规则:能被400整除,或能被4整除但不能被100整除
return (year % 400 == 0) || (year % 4 == 0 && year % 100 != 0);
}
2.2 每月天数计算方法
每月天数的处理有几种常见方法:
- 使用数组存储每月天数,索引对应月份
- 闰年时单独调整2月天数
- 使用条件判断逐月计算
数组法是最简洁高效的方式:
c复制// 平年各月天数(索引0不用,1-12对应1-12月)
int month_days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
// 闰年调整2月天数
if (is_leap(year)) {
month_days[2] = 29;
}
注意:数组大小设为13而不是12,是为了让月份直接对应数组索引,避免频繁的±1调整,减少出错概率。
3. 日期合法性校验的完整实现
3.1 校验逻辑分解
日期合法性校验需要检查三个维度:
- 年份范围:通常不做严格限制,但极端值(如负数)应考虑
- 月份范围:必须在1-12之间
- 日期范围:必须≥1且≤当月最大天数
c复制int validate_date(int year, int month, int day) {
// 校验月份
if (month < 1 || month > 12) return 0;
// 获取当月最大天数
int max_day = month_days[month];
if (month == 2 && is_leap(year)) {
max_day = 29;
}
// 校验日期
return (day >= 1 && day <= max_day);
}
3.2 边界条件测试用例
编写测试用例时应特别注意边界条件:
- 2月28日(平年)和29日(闰年)
- 各月最后一天和第一天
- 12月31日和1月1日
- 非法输入:月份13、日期0、负数等
code复制有效用例:
2000-2-29(闰年)
2023-4-30(小月最后一天)
1999-12-31(年末)
无效用例:
2023-2-29(平年2月)
2023-4-31(小月无31日)
2023-13-1(非法月份)
4. 经典问题:计算本年第几天
4.1 问题分析与算法设计
计算某日期是本年第几天的基本思路:
- 校验日期合法性
- 累加前month-1个月的天数
- 加上当月的day值
关键点在于:
- 闰年判断影响2月天数
- 月份天数累加时注意数组索引
- 输入校验要全面
4.2 完整代码实现与注释
c复制#include <stdio.h>
// 日期结构体(可选)
struct Date {
int year, month, day;
};
// 平年每月天数(索引0不用)
int month_days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int day_of_year(int year, int month, int day) {
// 闰年调整2月天数
month_days[2] = is_leap(year) ? 29 : 28;
// 校验日期合法性
if (!validate_date(year, month, day)) {
return -1; // 非法日期
}
// 计算本年第几天
int days = day;
for (int m = 1; m < month; m++) {
days += month_days[m];
}
return days;
}
int main() {
struct Date d;
while (scanf("%d%d%d", &d.year, &d.month, &d.day) != EOF) {
int days = day_of_year(d.year, d.month, d.day);
if (days == -1) {
printf("Input error!\n");
} else {
printf("%d\n", days);
}
}
return 0;
}
4.3 常见错误与调试技巧
- 数组越界:确保月份索引在1-12范围内
- 闰年判断错误:特别是能被100整除但不被400整除的年份
- 日期累加错误:注意循环终止条件是
m < month不是m <= month - 输入处理:使用
scanf时注意格式匹配,如"%d-%d-%d"处理带分隔符的输入
调试技巧:打印中间变量,如每月累加后的天数,帮助定位计算错误的位置。
5. 日期类扩展问题解析
5.1 日期加减运算
计算某日期之后x天的日期,算法步骤:
- 将原日期转换为"本年第N天"+x天
- 从1月1日开始,逐月减去天数,直到剩余天数≤当月天数
- 处理跨年情况
c复制void add_days(int year, int month, int day, int x) {
int total = day_of_year(year, month, day) + x;
int new_year = year;
// 处理跨年
while (total > (is_leap(new_year) ? 366 : 365)) {
total -= is_leap(new_year) ? 366 : 365;
new_year++;
}
// 计算新月份和日期
month = 1;
while (total > month_days[month]) {
total -= month_days[month];
month++;
}
day = total;
printf("%d-%d-%d\n", new_year, month, day);
}
5.2 时间加减运算
时间加减通常转换为最小单位(秒或分钟)计算:
c复制void add_minutes(int* hour, int* minute, int x) {
int total = *hour * 60 + *minute + x;
*hour = (total / 60) % 24; // 处理跨天
*minute = total % 60;
}
5.3 日期差值计算
计算两个日期之间的天数差:
- 分别计算两个日期到基准日期(如0001-01-01)的天数
- 求两个天数的差值
c复制int date_diff(int y1, int m1, int d1, int y2, int m2, int d2) {
return abs(days_since_epoch(y1,m1,d1) - days_since_epoch(y2,m2,d2));
}
6. 实战经验与优化技巧
6.1 预处理技巧
对于频繁使用的日期计算,可以预处理:
- 预计算闰年表
- 预存每月累积天数(避免重复计算)
- 使用查表法加速星期计算
c复制// 预存每月累积天数(平年)
int cum_days[13] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
int day_of_year_optimized(int year, int month, int day) {
int days = cum_days[month-1] + day;
if (month > 2 && is_leap(year)) days++;
return days;
}
6.2 输入处理技巧
处理不同格式的日期输入:
- 使用scanf模式匹配
- 处理分隔符(- / 空格等)
- 错误输入恢复
c复制// 处理YYYY-MM-DD格式输入
if (scanf("%d-%d-%d", &year, &month, &day) != 3) {
// 错误处理
}
6.3 测试用例设计
全面的测试用例应包含:
- 普通日期
- 闰年2月
- 各月首尾日期
- 跨年日期
- 非法输入
code复制测试用例示例:
2000-02-29(合法闰年)
1900-02-29(非法平年)
2023-04-31(非法日期)
0001-01-01(最小日期)
9999-12-31(最大日期)
在实际编码练习中,建议先手工计算几个测试用例的预期结果,再与程序输出对比,这是发现边界条件错误的有效方法。
