1. 题目背景与需求分析
leetcode 1450题"在既定时间做作业的学生人数"是一道典型的数组处理问题。题目描述如下:给定两个整数数组startTime和endTime,以及一个整数queryTime,要求统计有多少学生在queryTime时刻正在做作业。具体来说,当queryTime位于某个学生的[startTime[i], endTime[i]]区间内时,该学生就被计入统计。
这道题看似简单,但考察了几个核心编程能力:
- 数组的基本操作和遍历
- 区间判断的逻辑处理
- 边界条件的考虑
- 时间和空间复杂度的控制
在实际面试中,这类题目经常作为热身题出现,主要考察候选人对基础数据结构的掌握程度和编码的严谨性。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 暴力解法与直接实现
2.1 最直观的解法思路
最直接的解法是遍历所有学生的时间区间,逐个检查queryTime是否落在区间内:
python复制def busyStudent(startTime, endTime, queryTime):
count = 0
for i in range(len(startTime)):
if startTime[i] <= queryTime <= endTime[i]:
count += 1
return count
这种解法的时间复杂度是O(n),空间复杂度是O(1),对于题目给定的约束条件(n ≤ 100)来说完全足够。
2.2 边界条件与测试用例
在实现时需要考虑几种边界情况:
- queryTime正好等于某个startTime或endTime
- 空输入的情况
- 所有学生都不在queryTime做作业
- 所有学生都在queryTime做作业
对应的测试用例:
python复制assert busyStudent([1,2,3], [3,2,7], 4) == 1
assert busyStudent([4], [4], 4) == 1
assert busyStudent([], [], 1) == 0
assert busyStudent([1,1,1], [1,1,1], 1) == 3
assert busyStudent([9,8,7], [10,9,8], 7) == 0
3. 算法优化与进阶思考
3.1 可能的优化方向
虽然暴力解法已经足够高效,但我们可以思考一些优化方向:
- 预处理排序:如果数据规模很大,可以先对区间进行排序,然后使用二分查找快速定位相关区间
- 前缀和技巧:如果时间范围有限且已知,可以使用前缀和数组来统计每个时间点的学生数量
- 并行计算:对于超大规模数据,可以考虑并行化处理
3.2 排序+二分查找优化
假设我们允许预处理时间,可以先将区间排序,然后使用二分查找:
python复制import bisect
def busyStudent_optimized(startTime, endTime, queryTime):
startTime.sort()
endTime.sort()
# 查询startTime中<=queryTime的数量
left = bisect.bisect_right(startTime, queryTime)
# 查询endTime中<queryTime的数量
right = bisect.bisect_left(endTime, queryTime)
return left - right
这种解法预处理时间复杂度O(nlogn),查询时间复杂度O(logn),适合需要多次查询的场景。
4. 实际应用场景延伸
4.1 类似的实际问题
这类区间统计问题在实际开发中有很多应用场景:
- 会议室预订系统:查询某个时间段有多少会议室被占用
- 在线课程系统:统计同时在线学习的用户数
- 服务器监控:统计特定时刻活跃的连接数
- 交通流量分析:计算某时刻道路上的车辆数
4.2 系统设计中的扩展
在系统设计中,这类问题通常会使用以下技术:
- 线段树:用于高效处理区间查询和更新
- 差分数组:适用于固定范围的频繁区间更新
- 时间序列数据库:如InfluxDB专门优化了这类时间范围查询
5. 刷题技巧与学习建议
5.1 如何高效刷leetcode
- 分类练习:将题目按类型分组,集中攻克某一类问题
- 一题多解:对每道题尝试多种解法,比较优劣
- 总结模板:归纳常见问题的解题模式和代码模板
- 参加周赛:定期参加leetcode周赛锻炼实战能力
5.2 本题相关的学习路径
掌握这道题后,可以继续学习以下相关题目:
-
- 会议室(判断区间是否重叠)
-
- 会议室II(计算最大重叠数)
-
- 合并区间(区间合并操作)
-
- 无重叠区间(区间选择问题)
6. 编码风格与最佳实践
6.1 生产环境中的代码考量
在实际工程中,我们还需要考虑:
- 输入验证:检查startTime和endTime长度是否一致
- 异常处理:处理可能的无效输入(如endTime < startTime)
- 文档注释:添加清晰的函数说明和参数描述
- 性能监控:添加执行时间的日志记录
6.2 Pythonic的写法
更Pythonic的实现可以使用zip和生成器表达式:
python复制def busyStudent_pythonic(startTime, endTime, queryTime):
return sum(s <= queryTime <= e for s, e in zip(startTime, endTime))
这种写法简洁但可读性稍差,适合熟悉Python的团队使用。
7. 不同语言的实现对比
7.1 Java实现
java复制public int busyStudent(int[] startTime, int[] endTime, int queryTime) {
int count = 0;
for (int i = 0; i < startTime.length; i++) {
if (startTime[i] <= queryTime && queryTime <= endTime[i]) {
count++;
}
}
return count;
}
7.2 C++实现
cpp复制int busyStudent(vector<int>& startTime, vector<int>& endTime, int queryTime) {
int count = 0;
for (int i = 0; i < startTime.size(); i++) {
if (startTime[i] <= queryTime && queryTime <= endTime[i]) {
count++;
}
}
return count;
}
7.3 JavaScript实现
javascript复制function busyStudent(startTime, endTime, queryTime) {
let count = 0;
for (let i = 0; i < startTime.length; i++) {
if (startTime[i] <= queryTime && queryTime <= endTime[i]) {
count++;
}
}
return count;
}
8. 复杂度分析与算法选择
8.1 各种解法的复杂度对比
| 解法 | 预处理时间 | 查询时间 | 空间 | 适用场景 |
|---|---|---|---|---|
| 暴力法 | O(1) | O(n) | O(1) | 单次查询,n小 |
| 排序+二分 | O(nlogn) | O(logn) | O(1) | 多次查询,n大 |
| 前缀和 | O(n+k) | O(1) | O(k) | 时间范围k已知且小 |
8.2 如何选择合适的算法
选择算法时需要考虑:
- 数据规模:n的大小
- 查询次数:单次还是多次
- 时间范围:是否已知且有限
- 内存限制:是否能接受额外空间
对于leetcode这道题,由于n≤100且只需单次查询,暴力法是最合适的选择。
9. 常见错误与调试技巧
9.1 新手常见错误
- 数组越界:没有检查startTime和endTime长度是否一致
- 边界处理:queryTime正好等于startTime或endTime时的处理
- 索引错误:在循环中使用错误的索引
- 逻辑错误:区间判断条件写反(如把<=写成<)
9.2 调试建议
- 打印中间变量:在循环中打印关键变量值
- 使用小测试用例:先验证简单情况
- 边界测试:专门测试边界条件
- 代码复审:逐行检查逻辑是否正确
10. 单元测试与验证
10.1 完整的测试用例集
python复制import unittest
class TestBusyStudent(unittest.TestCase):
def test_normal_case(self):
self.assertEqual(busyStudent([1,2,3], [3,2,7], 4), 1)
def test_edge_time(self):
self.assertEqual(busyStudent([4], [4], 4), 1)
def test_empty_input(self):
self.assertEqual(busyStudent([], [], 1), 0)
def test_all_match(self):
self.assertEqual(busyStudent([1,1,1], [1,1,1], 1), 3)
def test_no_match(self):
self.assertEqual(busyStudent([9,8,7], [10,9,8], 7), 0)
if __name__ == '__main__':
unittest.main()
10.2 测试驱动开发
采用TDD方式开发时:
- 先编写测试用例,定义期望行为
- 实现最小功能使测试通过
- 逐步添加更多测试和功能
- 最后重构优化代码
这种方法能确保代码质量和正确性。
