刷题的时候遇到一道L1-024 后天 - 5分,题目短得就一句话:输入一个1到7的数字,表示今天是星期几,输出后天是星期几。5分,在一堆动辄几十分的大题里毫不起眼,但它几乎是新手第一次接触“周期映射”的最佳素材。我见过太多人在这种送分题上翻车——不是不会算,而是没理解星期的循环本质。这篇文章就把这道题彻底讲透,顺便聊聊竞赛里那些不起眼的得分细节。
这道题表面上是在考“后天是星期几”,实际上考的是模运算和边界处理。你不需要任何算法基础,只要知道星期一到星期日循环出现,就能写出来。但为什么有人会写成d+2直接输出?因为今天6、后天1,今天7、后天2,6+2=8、7+2=9,根本不在合法范围里。这就是所有周期问题的共同陷阱:超出范围后要绕回起点。
1. 题目到底在考什么:星期推算背后的周期思维
1.1 题目描述里的关键信息
原题一般是这样:输入一个整数d,1 <= d <= 7,d=1表示星期一,d=7表示星期日。要求输出后天对应的数字。
注意这里有个很容易忽略的点:题目给的是“后天”,不是“明天”。如果你只加1,那算的是明天;如果加2,才是后天。我见过有同学把d+1当成答案,样例输入1输出2,还觉得没问题,结果一提交 WA。为什么?因为后天是3,不是2。
还有一层隐藏信息:输入范围被限定在1~7,所以你不需要考虑星期八、星期九这种不存在的值。但输出同样必须落在1~7。换句话说,你拿到一个合法的输入,必须加工后给回一个合法的输出。怎么从“8”变回“1”,从“9”变回“2”?这就是循环。
1.2 星期的循环结构就是模运算
我们来列一个表:
| 今天 | 后天 |
|---|---|
| 1(一) | 3(三) |
| 2(二) | 4(四) |
| 3(三) | 5(五) |
| 4(四) | 6(六) |
| 5(五) | 7(日) |
| 6(六) | 1(一) |
| 7(日) | 2(二) |
看到没有,前五个数字都是d+2,但从6开始,结果变成了1和2。因为一周只有七天,走到第8天就是下一周的星期一。
计算机里处理这种“到顶回绕”最自然的方式就是取模。8 % 7 = 1,9 % 7 = 2,恰好对应星期一的1和星期二的2。但你如果直接写(d + 2) % 7,又会出问题:当d=5时,7 % 7 = 0,可答案明明是7。为什么?因为我们用的是1~7的编号,而不是0~6。取模运算天然把结果限制在0到6,而我们想要的是1到7。这就是这道题唯一真正的坑:编号起点不一致。
理解了这个,你就看穿了所有星期类问题的本质:本质上是一个长度为7的环形数组,你用下标0~6存七个星期名,输入是1~7,那就先把它减1转成下标,做完偏移后再加1转回人类编号。公式就是:
结果 = ( (今天 - 1 + 偏移天数) % 7 ) + 1偏移天数在后天场景下是2,所以就是:
结果 = ( (d - 1 + 2) % 7 ) + 1 = ( (d + 1) % 7 ) + 1我在草稿纸上验证一下:d=6,d+1=7,7%7=0,0+1=1。正确。d=7,d+1=8,8%7=1,1+1=2。正确。这个公式比(d+2)%7更可靠,因为通过“先减1、后加1”完成了从1起始到0起始再回到1起始的转换。
2. 两种主流解法:if条件分支与取模运算
2.1 最保险的if-else写法
刚接触编程的人,或者考试时心态偏保守的人,我建议直接用条件判断。思路非常直白:先算t = d + 2,如果t > 7,就减掉7。用C语言写:
#include <stdio.h> int main() { int d; scanf("%d", &d); int t = d + 2; if (t > 7) { t -= 7; } printf("%d\n", t); return 0; }还可以展开成完整的if-else if链:
#include <stdio.h> int main() { int d; scanf("%d", &d); if (d == 1) printf("3\n"); else if (d == 2) printf("4\n"); else if (d == 3) printf("5\n"); else if (d == 4) printf("6\n"); else if (d == 5) printf("7\n"); else if (d == 6) printf("1\n"); else printf("2\n"); return 0; }第二种写法看起来很傻,但在竞赛现场反而最不容易错。因为你把每一种输入对应的输出都提前算好了,边界天然不会越界。缺点就是代码啰嗦,如果题目改成“输入一个月份判断天数”,你还要写一堆分支,维护起来麻烦。
我个人的观点是:5分题不需要优美的解法,只需要最快的AC。如果你一眼能写出if判断,那就别在取模公式上犹豫。很多新手在考场上纠结“用取模会不会有bug”,白白浪费两三分钟。判题系统只看结果,不看实现。
2.2 更漂亮的取模写法
如果你想展示一点计算思维,或者想把代码写短,取模是更好的选择。
#include <stdio.h> int main() { int d; scanf("%d", &d); printf("%d\n", (d + 1) % 7 + 1); return 0; }如果使用 Python:
d = int(input()) print((d + 1) % 7 + 1)为什么是(d + 1) % 7 + 1?我推一遍:
- 目标是
d + 2,但结果落在1~7。 - 用
(d + 2)对7取模可以得到0~6,但满7会变0。 - 为了避开这个0,先把整个体系的起点从1挪到0:
(d - 1)。 - 移位后加2:
(d - 1 + 2)。 - 取模:
(d + 1) % 7。 - 最后把0的编号挪回1:
+1。
合并后就是(d + 1) % 7 + 1。这里最容易写错的是(d + 2) % 7,一旦输入5,输出0,判错。我当年也犯过这个错,还对着样例找了半天。
2.3 两种写法怎么选
很多人喜欢问“标准答案”是什么。其实没有标准答案,只有适合你的答案。我的建议是:
- 如果你对取模运算非常熟,一眼能推出
(d+1)%7+1,那直接用取模,代码短、不易手滑。 - 如果你对取模不熟,或者考试时间紧张,就用
if (t > 7) t -= 7;,逻辑直白,几乎不可能错。 - 如果你有强迫症,想练思维,那建议把取模公式背下来,因为后面做“一周后”“十天前”“每月第一天”这类题目时,通用公式能省很多事。
两种方式的时间复杂度都是 O(1),空间复杂度也是 O(1)。在这个场景下,性能差异完全不存在。
3. 我踩过的坑:边界值、映射和判题规则
3.1 星期天到底算7还是0
中文语境里经常有人说“周日是0”,因为很多编程语言里,日期库把星期日表示为0。比如某些系统的getDay()返回值,0是星期天。但这道题明确规定7是星期天,所以别把习惯带进来。任何和题目约定不一致的编号,都会导致诡异错误。
有个同学用数组char week[8][10]存星期名,按1~7赋值,下标0没管它。后来他改代码时想用取模,顺手写成d % 7,然后发现星期天输出成了0,数组越界读到乱码。这就是“编号不一致”的真实案例。所以用数组的同学,要么让下标0也放一个占位符,要么统一把编号减1。
3.2 输入输出格式中的隐藏坑
这类题目通常要求“在一行中输出一个整数”,没有多余文字。但有人会写成:
printf("后天是%d\n", result);如果判题系统是精确比对,这一行直接 WA。另外,如果题目备注“输入保证合法”,你甚至不需要处理非法输入;如果没备注,正规题目也隐含了“输入合法”的约定,你不需要写容错代码。把精力放在正常逻辑上。
还有一种情况是读入的时候用了getchar()或者混淆scanf和getchar,导致读到了换行符,d变成垃圾值。我用 C 语言刷题时,凡是输入单个整数,一律只写一行scanf("%d", &d);,不去整那些花活。Python 则一句话int(input())。
3.3 别在5分题上浪费罚时
竞赛判题有罚时机制,做错一次提交会加罚时。所以这种送分题,反而最考验“一次AC”的能力。我的考场习惯是:
- 读题,圈出“后天”“1~7”。
- 在草稿纸上写三组边界测试数据:
1、6、7。 - 确认自己的公式或if判断在这三个值上的输出分别是3、1、2。
- 编译运行,用样例测试,再用1、6、7测试。
- 确认没问题后再提交,别急着抢那几秒。
有人觉得5分题太简单,直接写完就交,结果边界错,被罚20分钟,心态直接崩。这很不值。越是小分题,越要稳。
还有一个细节:如果你用的是if-else if链,最后那个else可以覆盖所有剩余情况,不用再写d==7判断。这样既省代码,又不会因为d漏判而出错。
4. 从这道题延伸:任意天数、任意起点、日历推算
4.1 推广到任意偏移天数
这道题本质是:“今天星期d,k天后星期几”。k在本体里等于2。如果题目改成k天前或k天后,你只需要改偏移量。
通用公式:
结果 = ( (d - 1 + k) % 7 + 7 ) % 7 + 1为什么要多一个+7再取模?因为k可能是负数,比如“三天前”,(d - 1 + k)可能是负数。C语言和部分语言的%对负数的处理结果符号跟被除数走,可能出现负数。先加7再取模,保证结果在0~6内。
比如今天星期1,问4天后星期几。d=1,k=4:(1-1+4)%7+7)%7+1 = 4%7+7)%7+1 = (11)%7+1 = 4+1 = 5,是星期五,正确。
再看“10天后”:k=10,先算(d-1+10)%7也行,因为10不是负数。但如果统一用公式可以处理任意k,比如k=-3(三天前),d=1:(1-1-3 + 7) % 7 + 1 = 4%7+1 = 5?等等,三天前从星期一再往前三天,应该是星期五?我们验证:星期1(今天),昨天是星期日(7),前天是星期六(6),大前天是星期五(5)。结果5,正确。所以通用公式是对的。
4.2 处理“昨天”和“day after tomorrow”的映射
“后天”在英文里是day after tomorrow,偏移量是2。“昨天”是yesterday,偏移量是 -1。我在做日历类题目时,会先建一个小函数:
int shift_weekday(int weekday, int offset) { return ((weekday - 1 + offset % 7 + 7) % 7) + 1; }这里offset % 7可以先把偏移量降到 -6~6,避免大数运算。然后调用时,后天就是shift_weekday(d, 2),昨天就是shift_weekday(d, -1)。
这个函数在后续很多题目里都能复用。比如判断某年某月某日是星期几,你只需要先知道一个已知基准日,然后算出两个日期之间相隔多少天,再套这个函数偏移。很多新手把日历题想得很复杂,其实拆开后就两个点:两个日期之间的天数差和星期偏移函数。
4.3 更实际的应用:日期计算
如果你要处理真实日期而不是抽象的星期编号,那复杂程度会上一个台阶。因为牵扯到闰年、每月天数不同、世纪年规则。不过核心思想依然是“天数差 mod 7”。
比方说需要计算“从某活动日到今天过了多少天,今天星期几”,你可以:
- 把每个日期转换成从某个基准日(比如公元1年1月1日)开始的天数。
- 求两个总天数的差。
- 对差取模7。
- 用基准日的星期加上偏移。
这类题的经典公式是蔡勒公式,它能直接根据年月日算出星期几,输入是普通的日期,输出是0~6。公式长这样(格里高利历):
h = (d + 13*(m+1)/5 + K + K/4 + J/4 + 5*J) % 7其中m是月份,K是年份后两位,J是年份前两位。不过我不建议新手死记公式,容易记错。用“累计天数 + 取模”的思路去实现,反而更不容易忘。
回到L1-024,它把日期问题简化成最朴素的星期编号,考的就是这个映射能力。你在这个小问题上理解了,后面碰到涉及星期的题都能举一反三。
5. 竞赛新手视角:怎么用最短时间拿下送分题
5.1 考场上先挑软柿子捏
如果是比赛或者限时练习,遇到一套题,我会先把所有题看一遍,肉眼找出类似L1-024这种5分、10分的小题,先做掉。原因很简单:
- 送分题代码量小,出错概率低,能快速累积分数。
- 刚开始考试头脑清醒,不容易在简单逻辑上犯错。
- 先拿下一部分分数,后面做大题时心态更稳。
有人喜欢按照题号顺序一道一道做,结果在计算题上卡了40分钟,最后送分题都没时间写。我自己的习惯是:先把所有能一眼看穿逻辑的题全AC,再回头啃硬骨头。这不是投机取巧,是时间管理。
5.2 形成自己的模板
这类题做多了,你会发现很多题目都可以套用固定模板。比如处理星期、月份、循环移动,都是“映射到0起始数组 -> 偏移 -> 取模 -> 映射回原编号”三步。我在本地编辑器里存了一个常用的“星期偏移”代码片段,考试时插入即可,连推导都省了。
int shift(int weekday, int offset) { return ((weekday - 1 + offset % 7 + 7) % 7) + 1; }模板的价值不是让你偷懒,而是把已经验证过的正确逻辑固化。考试时间那么紧,没必要重新推导一个公式。如果你还没有自己的模板库,建议现在就开始积累,把每一道AC的小题都整理成“场景+代码片段”,日子久了效率会高很多。
5.3 忽略酷炫,正确优先
很多新手喜欢用一行取模表达式证明自己“懂算法”,结果写出来因为优先级问题编译不过,或者输出错误。我觉得在这个阶段,正确性远大于代码美感。
举个例子:
printf("%d\n", (d + 1) % 7 + 1);这个写法没问题。但如果你写成:
printf("%d\n", d + 1 % 7 + 1);因为运算符优先级,%比+高,所以实际计算的是d + (1%7) + 1 = d + 2,遇到星期6输出8,直接错。这就是“炫技”的代价。我见过不止一次。解决方式是:不确定优先级就加括号,甚至赋值给中间变量再输出。考官不会因为你多写两行而扣分,只会因为错误结果判你WA。
我还想说,这类5分题,题目名字里有个“后天”,很多人第一反应是查“后天”的日历,然后忘了输入是“1-7”而不是日期。你要时刻提醒自己:这里的输入是星期编号,不是日期。把题干里所有数字都当成数据,别脑补成真实日历。
写在最后的一点个人体会
这道题我后来在一套模拟练习里又遇到过,当时我用的是if版本,一分钟内AC。但那次之后我专门花时间验证了一遍取模写法,从此记住了(d + 1) % 7 + 1。后来的比赛里碰到“N天后星期几”的变体,我直接套通用公式,一次没错过。所以我的建议是:小题也要认真对待,至少把两种解法都跑通,理解它们的差异。特别重要的一个技巧是,提交前用1、6、7这三组边界值各测一遍,能挡住绝大多数边界bug。这个小习惯看上去不起眼,实际救了我很多次。毕竟,5分的题再简单,丢了也是5分;5分在高手如云的榜单里,可能正好决定你能不能跨过那条线。