一线城市的年轻人为了省钱,跟陌生人拼好床的帖子,我开始还以为是开玩笑,翻看之下,居然是真的。我真的震惊了!
刚刷到这个帖子,我第一反应就是:这也能拼?
一线城市房租贵,大家合租我能理解,客厅打地铺我都见怪不怪了。结果这次不是拼房,也不是拼卧室,是直接跟陌生人拼一张床。两个人错开时间睡,谁先下班谁先躺,另一个回来再换班,床单被子都不一定分得清。
我一开始还以为是网友编段子,往下翻才发现,人家真这么住。理由也很简单,一个月能省不少钱。
省钱省到这份上,已经不是吃不吃外卖的问题了。打工人白天在公司抢工位,晚上回家还得抢床位,房东看完估计都要研究新户型了:两平米,睡眠共享版。
一道看着像 SQL 的题,用 Python 写其实就是一次计数
数据一进来,目标很明确:找出选课人数不少于 5 人的课程。
原始记录大概长这样:
student class
A Math
B English
C Math
D Biology
E Math
F Computer
G Math
H Math
最后应该留下 Math,因为它刚好有 5 名学生。
这题第一眼看过去,很容易写成“两层循环”:拿出一门课,再把所有记录扫一遍,数有多少学生选了它。数据少的时候没问题,数据一大就有点难看了。
假设一共有 n 条选课记录,课程种类也接近 n,两层循环最差要跑到 O(n²)。这种代码能过小样例,我一般也不会留在项目里。
Python 里直接用字典计数更稳。
deffind_popular_classes(enrollments):
class_count = {}
for record in enrollments:
course = record["class"]
class_count[course] = class_count.get(course, 0) + 1
result = []
for course, student_total in class_count.items():
if student_total >= 5:
result.append(course)
return result
测试数据:
records = [
{"student": "A", "class": "Math"},
{"student": "B", "class": "English"},
{"student": "C", "class": "Math"},
{"student": "D", "class": "Biology"},
{"student": "E", "class": "Math"},
{"student": "F", "class": "Computer"},
{"student": "G", "class": "Math"},
{"student": "H", "class": "Math"},
]
print(find_popular_classes(records))
输出:
['Math']
这段代码只扫了两遍数据。第一遍统计课程人数,第二遍过滤人数不少于 5 的课程,时间复杂度是 O(n),额外空间取决于课程数量。
不过这里还有个容易被忽略的问题:同一名学生可能重复提交同一门课。
比如数据里出现两条:
{"student": "A", "class": "Math"}
{"student": "A", "class": "Math"}
直接计数会把 A 算两次。题目如果保证记录唯一,前面的写法够用;要是不保证,我会先按“学生加课程”去重,再统计。
deffind_popular_classes(enrollments):
valid_records = set()
for record in enrollments:
student = record["student"]
course = record["class"]
valid_records.add((student, course))
class_count = {}
for _, course in valid_records:
class_count[course] = class_count.get(course, 0) + 1
return [
course
for course, student_total in class_count.items()
if student_total >= 5
]
这里用集合保存 (student, course),相同学生重复选择同一门课,只会保留一份。
题目本身不复杂,真正要判断的是“统计记录数”还是“统计不同学生数”。这两个需求只差一个去重,结果可能完全不一样。
碰到计数题,我一般先确认统计对象,再决定字典的键怎么设计。键选错了,后面写得再漂亮也只是稳定地算错。