CURATED GUIDE · 固定上游版本

JavaScript 算法与数据结构学习路线

用 8 周串联复杂度、核心数据结构、经典算法和项目练习,配固定版本源码入口、边界测试与自测清单。

这是一份以动手实现和边界测试为主线的 8 周导览。它不替代上游源码,也不要求先刷几百道题;每周只聚焦一组概念,并留下能运行、能解释、能复测的练习产出。

你最终会获得什么

1. 开始之前:准备环境与学习方法

先修知识

开始路线前,确认自己已经理解变量与函数、数组和对象、Map 与 Set、类、模块、递归以及基本异常处理。暂时不熟悉异步编程不影响本路线;先掌握同步数据结构,再学习复杂应用。

上游固定版本要求 Node.js 22 或更高版本、npm 10 或更高版本。以下命令会取得完整仓库并切换到本文核对的 Commit:

git clone https://github.com/trekhleb/javascript-algorithms.git
cd javascript-algorithms
git checkout 85293e3e2b88f4d2ce330d956b139cf628aa1e82
npm ci
npm test -- --runInBand

上游提供简体中文说明。建议先读任务说明和测试,再独立实现,最后才对照源码;不要从复制答案开始。

每个主题的固定练习循环

  1. 描述问题:写清输入、输出、约束、边界和是否允许修改原数据。
  2. 先猜成本:在实现前写下预期时间复杂度和额外空间复杂度。
  3. 写最小实现:先让普通样例通过,再处理空输入、重复值和异常分支。
  4. 补自动化测试:至少包含一个正常用例、三个边界用例和一个反例。
  5. 对照与复述:比较上游实现,合上书重新解释关键不变量,并记录自己曾犯的错误。

每周建议投入 5–7 小时:两次各 60–90 分钟用于学习,两次用于编码,最后一次只做测试和复盘。时间不足时减少主题数量,不省略测试。

2. 复杂度:先学会比较方案

大 O 描述输入规模增长时资源消耗的增长趋势,不等于某一次运行的精确毫秒数。O(1) 也可能比小输入下的 O(n) 慢,比较方案时必须同时说明数据规模和常数开销。

分析递归时,同时写出单次调用成本、调用次数和递归深度。分析空间时,不遗漏递归调用栈、辅助数组、字符串拼接和返回值本身。

练习:分别写出一个 O(n) 和一个 O(n²) 的重复元素检测函数,用相同输入比较结果;再使用 Set 改进方案,并说明平均成本为什么变化。

3. 八周学习路线

第 1 周:链表、栈与队列——理解引用和顺序

精选入口:单向链表。

第 2 周:哈希表、堆与优先队列——按特征选结构

精选入口:哈希表。

第 3 周:二叉搜索树、树遍历与前缀树——掌握递归结构

精选入口:二叉搜索树。

第 4 周:查找与排序——比较不变量和成本

精选入口:线性搜索。

第 5 周:递归、分治与回溯——管理搜索状态

精选入口:递归爬楼梯、N 皇后。

第 6 周:动态规划——识别重复子问题

精选入口:最大子数组。

第 7 周:图搜索与最短路径——处理连接关系

精选入口:图。

第 8 周:综合项目与面试表达——把知识连起来

本周不增加新算法,完成一个“带优先级的依赖任务调度器”:任务有名称、优先级和前置依赖;只有依赖完成后才能执行,同批可执行任务按优先级选择。

4. 测试清单:正确性先于技巧

上游使用 Jest。为自己的实现建立统一样例,让不同算法在相同输入上接受同样检查,而不是只验证一个演示数组。

test.each([
  [[], []],
  [[3], [3]],
  [[2, 2, 2], [2, 2, 2]],
  [[1, 2, 3], [1, 2, 3]],
  [[3, -1, 2], [-1, 2, 3]],
])('sorts %p', (input, expected) => {
  expect(mySort(input)).toEqual(expected);
});

每个主题至少检查以下项目:

性能测试单独记录环境、输入规模和重复次数,不把一次 console.time() 当作复杂度证明。

5. JavaScript 常见陷阱

6. 自测:不看答案能否讲清楚

完成 8 周后,随机抽取以下问题。能写代码之外,还要能说明约束和权衡:

  1. 为什么有序数组可以二分查找,而单向链表通常不适合同样优化?
  2. 删除单向链表节点时,为什么头节点和尾节点需要分别检查?
  3. 堆为什么适合反复取最值,却不适合任意元素快速查找?
  4. 二分查找的区间定义如何影响左右边界更新和终止条件?
  5. 回溯中的“撤销选择”具体恢复哪些状态?浅拷贝是否足够?
  6. 动态规划的状态、转移、初始值和遍历顺序分别是什么?
  7. BFS 为什么能得到无权图最少边数?已访问标记应何时设置?
  8. 拓扑排序为什么能检测环?Dijkstra 为什么要求非负边权?
  9. 你的调度器如何处理多个合法执行顺序,测试是否过度依赖某一种顺序?
  10. 如果输入扩大 100 倍,哪一步最先成为瓶颈,如何用复杂度分析和测量验证?

不能回答的问题回到对应周补一个最小实验,不盲目增加刷题数量。

7. 来源、版本与许可

本文依据上游固定版本的目录、主题和仓库使用方式重新编写学习顺序、练习与自测,不复制其算法实现。上游源码、测试和文档的再利用遵循其 MIT 许可;本文中的外部链接仅作为对应版本的阅读入口。

离线保存这份精选导览

关注公众号「日拱一卒」,发送口令「JS算法」,按提示验证后下载 Markdown 或 PDF。

前往下载