🎄Advent of Code 2025 挑战全手写代码 Day 5 - 餐厅
Welcom back! 今天题目 Cafeteria 难度简单(一星 ⭐),主要考察排序、集合、区间合并等知识点。

📖 题目速览
题目地址:https://adventofcode.com/2025/day/5
我们终于进入了传说中的“秘密餐厅”!但是,因为新的库存管理系统崩溃了,精灵大厨们分不清哪些食材是新鲜(Fresh)的,哪些是变质(Spoiled)的。为了吃上饭,我们得帮他们修 Bug。
Part 1:点在区间内(Point in Interval)
输入包含两部分:
- 一组新鲜食材的 ID 区间(例如
3-5,10-14)。 - 一组现有库存的 ID(例如
1,5,8)。
规则很简单:如果一个库存 ID 落在任何一个新鲜区间内,它就是新鲜的。
任务:统计现有库存中有多少个 ID 是新鲜的。
💡 Part 1 优化思路:
最直接的做法是双重循环:遍历每个 ID,再遍历每个区间检查。复杂度是 O ( N × M ) O(N \times M) O(N×M)。
但其实我们可以预先对区间进行排序(按起始位置升序排列),然后对每个 ID 使用二分查找或者将区间合并后查找。这样可以将时间复杂度从线性降到对数!
不过由于 Part 1 的数据量其实很小,暴力法也没问题。
Part 2:区间并集的长度(Length of Union of Intervals)
精灵们为了省事,想知道总共有多少个整数 ID被认为是新鲜的。
也就是说,我们要计算所有给定的新鲜区间的并集包含了多少个整数。
在这一部分,现有库存的 ID就没有用了,我们只需要(已经排好序的)新鲜食材的 ID 区间。
例如:
[3, 5] 和 [4, 6] 合并后是 [3, 6],包含 3, 4, 5, 6 共 4 个数。
🚫 我的踩坑时刻
这题我一开始写了个 Bug,在合并区间时:
if start - target[1] <= 1:
target = target[0], end # ❌ 错误写法!
我直接把当前区间的 end 赋给了合并后的终点。
但这忽略了一种情况:如果当前区间完全被包含在目标区间内(例如 target=[1, 10], current=[3, 5]),这样做会导致合并后的区间反而变小了(变成了 [1, 5])!这导致得到的结果比正确结果要小很多!
✅ 正确写法应该是取最大值:
target = target[0], max(target[1], end)
就差这一个 max,让我调试了好半天… 😅
💻 为什么不用 Set?
为什么不直接把所有区间里的数都塞进一个 set 里去重,最后求 len(set)?
对于小数据量这当然没问题。但如果区间范围非常大(例如 1-1000000000),用 Set 会造成极大的内存空间浪费,因为我们实际上并不需要知道每个ID的具体数值。
区间合并算法的优势在于:
它只处理区间的“端点”,与区间内的具体数值跨度无关。
无论区间是 1-5 还是 1-100亿,对算法来说都只是处理两个数字。
- 时间复杂度: O ( N log N ) O(N \log N) O(NlogN),主要花在排序上。
- 空间复杂度: O ( 1 ) O(1) O(1) 。
✨ 核心代码 (Python)
def solve_part2(ranges):
# 1. 必须按起始位置排序!
ranges.sort(key=lambda x: x[0])
out = 0
target = ranges[0]
for idx in range(1, len(ranges)):
start, end = ranges[idx]
# 2. 判断是否重叠或相连(离散整数,差1也算连着)
if start - target[1] <= 1:
# 3. 核心:取最大的结束位置
target = target[0], max(target[1], end)
else:
# 4. 结算上一段区间长度
out += target[1] - target[0] + 1
target = start, end
# ‼️别忘了加上最后一段
out += target[1] - target[0] + 1
return out
“极限”优化
实际上我们可以再进一步优化!将 part 1 的排序和 part 2 的区间合并提前到数据准备阶段,这样在 part 1 我们可以通过前面说过的二分查找法确定某个 ID 是否在某个(合并后的)区间内;而 part 2 就更简单了,对每一个(合并后)的区间,直接尾部减头部再加一,就是区间内 ID 总数!
# 二分查找示例代码
def _binary_search(self, lower: int, upper: int, target: int) -> bool:
"""Binary search to check if target is in the range [lower, upper]."""
while lower <= upper:
mid: int = (lower + upper) // 2
if self.ranges[mid][0] <= target <= self.ranges[mid][1]:
return True
elif target < self.ranges[mid][0]:
upper = mid - 1
else:
lower = mid + 1
return False
总结
今天这题虽然也是 1 星难度,但很好地考察了贪心、区间和排序的思想,是 LeetCode 56 (Merge Intervals) 的变种。
完整代码:访问 github
Happy Coding! 今天也要继续冲榜!🚀

606

被折叠的 条评论
为什么被折叠?



