基于遗忘曲线的智能刷题计划:间隔重复是效率的核心杠杆
📅 2026/7/9 0:28:14
👁️ 阅读次数
📝 编程学习
基于遗忘曲线的智能刷题计划:间隔重复是效率的核心杠杆
一、刷 100 道题不如把 20 道题刷三遍
大多数刷题计划的逻辑是线性的:按标签分类,逐个攻克。数组刷完刷链表,链表刷完刷树,树刷完刷 DP。平均下来每道题只过一遍,几天后基本忘光。
这不是记忆差的问题,是复习策略的问题。认知心理学的研究表明:信息遗忘遵循指数衰减曲线,但通过间隔重复(Spaced Repetition),可以在遗忘临界点介入,大幅降低遗忘率。这篇文章讨论如何将遗忘曲线理论应用到刷题计划中,实现智能化的复习调度。
二、遗忘曲线与间隔重复
graph LR subgraph 不复习的情况 A1[初次学习: 记忆强度 100%] -->|1天后| B1[记忆强度 40%] B1 -->|3天后| C1[记忆强度 20%] C1 -->|7天后| D1[记忆强度 5%] end subgraph 间隔重复的情况 A2[初次学习] -->|1天后复习| B2[记忆强度回升至 90%] B2 -->|3天后复习| C2[记忆强度回升至 95%] C2 -->|7天后复习| D2[记忆强度稳定在 98%] end艾宾浩斯遗忘曲线的核心发现是:
- 遗忘在学完后立即开始,前期速度很快。
- 每次复习都能减缓遗忘速度,且复习间隔可以逐次拉长。
- 在记忆即将遗忘时进行复习,效果最好(即「合意困难」)。
三、实现:基于 SM-2 的智能调度器
from dataclasses import dataclass from datetime import datetime, timedelta from typing import Optional import heapq @dataclass class ProblemRecord: """单道题目的学习记录""" problem_id: int problem_name: str tags: list[str] # SM-2 算法的核心参数 easiness_factor: float = 2.5 # 难度因子,越高 = 越容易 interval_days: int = 0 # 当前复习间隔(天) repetitions: int = 0 # 连续正确次数 next_review_at: Optional[datetime] = None # 下次复习时间 last_review_at: Optional[datetime] = None # 上次复习时间 class SpacedRepetitionScheduler: """基于 SM-2 算法的复习调度器 SM-2 是 SuperMemo 第二版算法,由 Piotr Wozniak 设计。 核心思想:根据每次答题的质量(0-5 分)动态调整复习间隔。 """ # 答题质量对应的间隔倍数 # quality: 0=完全忘记, 3=正确但有困难, 5=完美作答 QUALITY_MULTIPLIER = { 0: 0.0, # 完全忘记 → 重置 1: 0.0, # 错误但正确答案看起来熟悉 2: 0.0, # 错误但记得曾经会做 3: 0.8, # 正确但很困难 4: 1.0, # 正确,稍有犹豫 5: 1.2, # 完美作答 } def schedule_review( self, record: ProblemRecord, quality: int ) -> ProblemRecord: """根据答题质量安排下次复习 Args: record: 当前题目的学习记录 quality: 答题质量评分 0-5 Returns: 更新后的学习记录 """ # 1. 更新难度因子 # EF' = EF + (0.1 - (5-q) * (0.08 + (5-q) * 0.02)) # 这个公式的意义:低质量降低 EF,高质量提升 EF old_ef = record.easiness_factor record.easiness_factor = max(1.3, old_ef + ( 0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02) )) # 2. 根据质量决定间隔 if quality < 3: # 答题不理想,重置学习进度 record.repetitions = 0 record.interval_days = 1 # 明天再来 else: # 答题通过,增加间隔 if record.repetitions == 0: record.interval_days = 1 elif record.repetitions == 1: record.interval_days = 3 # 第 2 次正确后 3 天复习 else: # interval = 上次 interval × easiness_factor record.interval_days = int( record.interval_days * record.easiness_factor ) record.repetitions += 1 # 3. 计算下次复习时间 now = datetime.now() record.last_review_at = now record.next_review_at = now + timedelta(days=record.interval_days) return record class ReviewQueue: """复习队列:按紧急程度排序的优先队列 使用最小堆,next_review_at 最早的排在堆顶。 """ def __init__(self): self._heap: list[tuple[float, int, ProblemRecord]] = [] def push(self, record: ProblemRecord) -> None: """加入复习队列""" if record.next_review_at is None: return priority = record.next_review_at.timestamp() # 使用 id 作为二级排序,避免 ProblemRecord 不可比较的问题 heapq.heappush( self._heap, (priority, id(record), record), ) def pop_due(self) -> list[ProblemRecord]: """弹出所有到期的复习任务""" due = [] now = datetime.now().timestamp() while self._heap and self._heap[0][0] <= now: _, _, record = heapq.heappop(self._heap) due.append(record) return due def get_daily_plan(self) -> list[ProblemRecord]: """获取今日复习计划""" return self.pop_due() # ---- 使用示例 ---- if __name__ == "__main__": scheduler = SpacedRepetitionScheduler() queue = ReviewQueue() # 学习 3 道题 problems = [ ProblemRecord(problem_id=1, problem_name="两数之和", tags=["数组", "哈希表"]), ProblemRecord(problem_id=2, problem_name="反转链表", tags=["链表"]), ProblemRecord(problem_id=3, problem_name="爬楼梯", tags=["DP"]), ] # 模拟第一轮学习后的评价 qualities = [4, 3, 5] # 分别是:正确稍有犹豫、正确但困难、完美 for prob, q in zip(problems, qualities): updated = scheduler.schedule_review(prob, q) queue.push(updated) print( f"{prob.problem_name}: " f"EF={prob.easiness_factor:.1f}, " f"间隔={prob.interval_days}天, " f"下次复习={prob.next_review_at.strftime('%m-%d')}" ) # 输出示例: # 两数之和: EF=2.4, 间隔=1天, 下次复习=07-08 # 反转链表: EF=2.4, 间隔=1天, 下次复习=07-08 # 爬楼梯: EF=2.6, 间隔=1天, 下次复习=07-08 # 获取今日计划 today_plan = queue.get_daily_plan() print(f"\n今日复习: {len(today_plan)} 道题")四、边界与优化
4.1 SM-2 的局限性
SM-2 是 1987 年的算法,设计时主要针对单词记忆。对于算法刷题,需要额外考虑:
- 题目之间有依赖关系(先掌握链表再学树的遍历更容易)
- 同类题目的"迁移效应"(做会一道 DP 题可能让你对同类题更有信心)
4.2 冷启动问题
新题目的初始难度因子默认为 2.5。如果题目对你来说特别简单或特别难,前几次的调度会不准确。解决方案是根据题目的难度标签(Easy/Medium/Hard)预设初始 EF 值。
4.3 计划容量控制
如果每天的复习任务过多(比如之前欠了很多题),会导致「复习债」积压。需要有容量的上限控制——当每日计划超过合理数量时,优先安排最紧急(最接近遗忘临界点)的题目。
4.4 扩展:动态难度调整
将 SM-2 的 0-5 质量评分扩展为更细粒度的指标:
- 独立完成时间
- 是否需要提示
- 边界用例是否一次性通过
这些指标的加权可以给出更准确的质量评分。
五、总结
刷题的效率不取决于总量,取决于每道题在长期记忆中的留存率。基于遗忘曲线的调度系统,核心价值在于:在你即将忘记一道题之前,精准地提醒你复习。SM-2 算法虽然简单,但背后的思想——用数学方法量化「什么时候该复习」——是构建任何学习系统的基础。
编程学习
技术分享
实战经验