LeetCode 1118. 一月有多少天
题目描述
题意分析
给定年份
year和月份month(1 到 12),返回该年该月的天数。这道题的信息量几乎全在「哪些是常量、哪些是规则」的划分上。十二个月里有十一个月的天数与年份无关,是彻底的常量;只有二月依赖年份,取 28 或 29。唯一的变量是「这一年是不是闰年」,除此之外没有任何需要计算的东西。
闰年的判定规则本身是题目给定的历法知识而非可推导的数学:能被 4 整除的是闰年,但能被 100 整除的不是,能被 400 整除的又是。三条规则是层层例外的关系,写成布尔表达式时的组合顺序会直接决定对错——这就是本题真正的考点,也是它虽然标简单却常被写错的原因。
约束里
year的范围是 1583 到 2100。这个下界不是随便定的:1582 年格里高利历改革之前用的是儒略历,闰年规则里没有「百年不闰」这一条,题目通过限定范围避免了历法切换带来的歧义。上界 2100 则恰好包含一个「能被 100 整除但不能被 400 整除」的年份,说明测试数据一定会覆盖这个最容易写错的分支。边界:
month保证在 1 到 12 之间,不需要做参数校验;2100 年是平年(能被 100 整除、不能被 400 整除),2000 年是闰年(能被 400 整除),这两个值是必须自测的用例。
解法:闰年判断
核心思路
月份天数中,只有二月受年份影响。用长度为 13 的表保存平年各月天数,第 0 位占位,使月份可以直接作为下标;然后只处理“闰年二月加一天”这一条例外。
格里高利历的闰年条件为:
\[leap=(year\bmod 400=0)\lor(year\bmod 4=0\land year\bmod 100\ne0)\]第一项保留 400 的倍数,例如 2000;第二项接受普通的 4 的倍数,同时排除 1900、2100 这类非 400 倍数的整百年。括号体现了“能被 400 整除,或能被 4 整除但不能被 100 整除”的完整规则。
正确性按月份分类即可:非二月直接查表,表值固定正确;二月若
leap为真返回 29,否则读取表中的 28。两类覆盖所有合法输入,因此算法总能返回正确天数。
解题步骤
- 建立
days = {0,31,28,31,30,31,30,31,31,30,31,30,31},把 28 作为二月默认值。- 计算
leap = year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)。- 若
month == 2 && leap,返回 29;否则返回days[month]。边界样例必须覆盖整百年:
2000-02命中 400 规则,返回 29;1900-02与2100-02被 100 规则排除,返回 28;2024-02返回 29;2023-02返回 28。还应检查2000-01返回 31,避免把“闰年”错误地作用于所有月份。
代码实现
class Solution {
public int numberOfDays(int year, int month) {
int[] days = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
boolean leap = (year % 400 == 0)
|| (year % 4 == 0 && year % 100 != 0);
if (month == 2 && leap) {
return 29;
}
return days[month];
}
}
func numberOfDays(year int, month int) int {
days := []int{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
leap := year%400 == 0 || (year%4 == 0 && year%100 != 0)
if month == 2 && leap {
return 29
}
return days[month]
}
复杂度分析
- 时间复杂度:$O(1)$,只进行常数次取模、判断和数组访问。
- 空间复杂度:$O(1)$,月份表长度固定为 13。
关键点总结
- 固定月份数据用表表达,控制流只保留二月的闰年例外。
- 闰年条件是“400 的倍数,或 4 的倍数且不是 100 的倍数”,括号不能改变。
- 第 0 位占位让月份与下标一致,避免
month - 1的差一错误。- 面试自测至少覆盖普通年、普通闰年、非闰整百年和 400 倍数年。
易错点总结
- 只判断
year % 4 == 0:1900-02、2100-02会被误判为 29 天。- 漏掉 400 的例外:
year % 4 == 0 && year % 100 != 0会把2000-02误判为 28 天。- 错写括号:
(year % 400 == 0 || year % 4 == 0) && year % 100 != 0同样会排除 2000 年。- 只判断闰年、不判断
month == 2:2000-01会错误返回 29。- 表首有占位元素时必须访问
days[month];若改用 12 个元素,则必须访问days[month-1],两种约定不能混用。
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| 1154. 一年中的第几天 | 简单 | 同样依赖闰年判定,但需要把前几个月的天数累加,多一层前缀和思路 |
| 1360. 日期之间隔几天 | 简单 | 把日期折算成「距某个基准日的天数」再相减,闰年规则要反复使用 |
| 507. 完美数 | 简单 | 同为规则型数学题,考点在因数枚举的边界而非历法 |
| 728. 自除数 | 简单 | 逐位取模判断,练习「把规则准确翻译成布尔表达式」 |
| 168. Excel 表列名称 | 简单 | 从 1 起编号的进制转换,与本题「表首位补 0」同属下标对齐类陷阱 |
| 412. Fizz Buzz | 简单 | 多条整除规则叠加,判断顺序写反就会出错,与闰年的层层例外同构 |