freeCodeCamp 算法题精讲:"Where do I Belong" 查找数组插入下标的完整实现
本篇以 freeCodeCamp 课程库中的算法题 "Where do I Belong"(id: a24c1a4622e3c05097f71d67)为主体,完整讲解这道"排序后求插入下标"题目的题面定义、全部验收测试、官方参考实现的逐行解析,以及题目元数据在课程体系中的实际归属位置。读完你可以掌握该题的完整判题标准、常见边界陷阱(重复值、空数组、越界插入),并能独立写出通过所有断言的解法。
题目定义:求值在排序后数组中的最低插入下标
题目源文件位于 curriculum/challenges/english/blocks/basic-algorithm-scripting/a24c1a4622e3c05097f71d67.md,其 --description-- 部分给出的完整题面如下:
Return the lowest index at which a value (second argument) should be inserted into an array (first argument) once it has been sorted. The returned value should be a number.
(返回一个值(第二个参数)在被插入到数组(第一个参数)并完成排序后,应当占据的最低下标。返回值必须是一个数字。)
题面给出了两个关键示例,直接点明了算法的两个核心步骤——先排序,再找位置:
getIndexToIns([1, 2, 3, 4], 1.5)应返回1,因为1.5大于1(下标 0)而小于2(下标 1);getIndexToIns([20, 3, 5], 19)应返回2,因为数组排序后变为[3, 5, 20],而19小于20(下标 2)且大于5(下标 1)。
这里有两个容易被忽略的隐含约定:
- 输入数组本身可能未排序,因此"排序"是解题的第一步,不能假设有序输入;
- 返回的是"最低下标",这意味着当待插入值与数组中已有元素相等时,应当返回该元素首次出现的位置(前插而非后插),这一语义由
>=比较来保证,后文会展开。
挑战种子代码:你需要修改的起点
题目的 --seed-contents-- 部分提供了编辑器的初始代码,这是一个返回了错误结果的占位实现:
function getIndexToIns(arr, num) {
return num;
}
getIndexToIns([40, 60], 50);
任务就是把函数体替换为正确的逻辑,使文件末行的调用(以及判题系统注入的全部断言)都返回正确结果。种子代码同时固定了两个事实:函数名为 getIndexToIns、参数顺序为 (arr, num),判题断言都依赖这一签名。
官方参考实现:排序 + 线性扫描
--solutions-- 部分给出的官方参考解法只有 10 行,值得逐行拆解:
function getIndexToIns(arr, num) {
arr = arr.sort((a, b) => a - b);
for (let i = 0; i < arr.length; i++) {
if (arr[i] >= num) {
return i;
}
}
return arr.length;
}
getIndexToIns([40, 60], 50);
逐段分析其设计意图:
arr.sort((a, b) => a - b):使用比较函数按数值升序排序。这一步不能省略——若直接对原数组做sort(),JavaScript 会按 UTF-16 码元排序(字典序),[20, 3, 5]会变成[20, 3, 5]的字符串序结果而非[3, 5, 20]。注意这里把排序结果重新赋值给arr,而不是原地依赖返回值,语义上是"取到排序后的引用再扫描"。for循环 +if (arr[i] >= num) return i:从前往后扫描,找到第一个不小于num的元素,立即返回其下标。>=是"最低下标"语义的关键:当arr[i] === num时也命中,返回的正是等值元素的最左侧位置。return arr.length:循环结束仍未命中,说明num大于数组中所有元素,插入位置只能是末位之后,下标即arr.length。空数组也自然落入此分支,返回0,与getIndexToIns([], 1)的断言一致。
一个可讨论的细节:对入参的排序是原地操作
参考解法直接调用了 arr.sort(...),而 JavaScript 的 Array.prototype.sort 是原地排序,会修改调用方传入的数组。由于本函数的返回值只依赖排序后的内容,且判题断言每次都用字面量数组调用、不会跨断言复用同一个数组对象,因此通过全部测试没有问题。但从函数纯度角度看,更稳妥的写法是先复制再排序,例如 const sorted = [...arr].sort((a, b) => a - b);,避免污染调用方的数据。这属于解法层面的工程考量,不影响本题的判题结果。
判题断言全景:12 组测试覆盖的边界条件
--hints-- 部分列出了判题系统的全部断言(assert.isNumber 校验返回类型,assert.strictEqual 校验精确值)。完整覆盖如下表,右列是对应的边界语义:
| 测试输入 | 期望返回值 | 覆盖的边界/语义 |
|---|---|---|
getIndexToIns([10, 20, 30, 40, 50], 35) |
3 |
插入到中间空档 |
getIndexToIns([10, 20, 30, 40, 50], 30) |
2 |
重复值:返回等值元素最低下标(>= 语义) |
getIndexToIns([40, 60], 50) |
1 |
双元素数组中间插入 |
getIndexToIns([3, 10, 5], 3) |
0 |
未排序输入 + 插入到首位(排序后 3 在 [3,5,10] 的 0 位) |
getIndexToIns([5, 3, 20, 3], 5) |
2 |
未排序 + 重复值:排序后 [3,3,5,20],5 命中下标 2 |
getIndexToIns([2, 20, 10], 19) |
2 |
未排序输入,插入末位之前 |
getIndexToIns([2, 5, 10], 15) |
3 |
大于所有元素:走 return arr.length 分支 |
getIndexToIns([], 1) |
0 |
空数组:同样走 return arr.length 分支 |
每一组输入都额外配有一条 assert.isNumber(...) 断言,确保返回类型是数字而非布尔值或 undefined。这 8 组输入恰好构成一个完备的测试矩阵:已排序/未排序 × 中间插入/首位插入/末位追加 × 有重复/无重复 × 空数组。
题目元数据与课程归属位置
挑战文件的 YAML front matter 声明了其元数据:
---
id: a24c1a4622e3c05097f71d67
title: Where do I Belong
challengeType: 1
forumTopicId: 16094
dashedName: where-do-i-belong
---
这些字段并非随意填写,而是由课程模式进行校验的。在 curriculum/schema/challenge-schema.js 中,id、title、challengeType、dashedName、forumTopicId 都是受 Joi 模式约束的字段,其中 dashedName 需要匹配 slug 正则(对应本例的 where-do-i-belong),forumTopicId 用于关联社区论坛讨论帖。
challengeType: 1 的含义可以在 packages/shared/src/config/challenge-types.ts 中确认:该文件定义了全部挑战类型的数值映射,其中 const js = 1,即类型 1 代表一道 JavaScript 编码题——这类题目在编辑器中运行并由断言判分。
课程结构上,从 curriculum/structure/blocks/basic-algorithm-scripting.json 的 challengeOrder 数组可以看到,"Where do I Belong" 是 Basic Algorithm Scripting 模块 16 道题中的第 14 道,紧跟 "Falsy Bouncer" 之后、"Mutations" 之前;而该模块又是 curriculum/structure/superblocks/javascript-algorithms-and-data-structures.json 所定义的 "JavaScript Algorithms and Data Structures" 认证的第 6 个 block。从选题顺序看,此时学习者已经掌握了数组方法(如 "Slice and Splice")与条件判断、循环,正好具备完成"排序 + 扫描"解法的知识基础。
实现要点自查清单
写这个函数时,可以用以下清单对照自检(每条都对应至少一条判题断言):
- 先排序再比较——
getIndexToIns([3, 10, 5], 3)要求处理未排序输入; - 用
>=而非>判断——getIndexToIns([10, 20, 30, 40, 50], 30)要求重复值时返回最低下标2而非跳过; - 循环走完仍有出口——
getIndexToIns([2, 5, 10], 15)和getIndexToIns([], 1)都依赖return arr.length这一兜底返回; - 比较必须是数值比较——题面示例
getIndexToIns([1,2,3,4], 1.5)说明待插入值可能是小数,排序比较函数(a, b) => a - b与线性比较都应保持数值语义; - 返回值是数字——任何提前
return或遗漏的分支都不能返回undefined,否则assert.isNumber会失败。
小结
"Where do I Belong" 题面简短,但它把三个常见工程点浓缩在了一道题里:输入无序时的预处理(数值排序)、相等值的"前插"语义(>= 比较)、以及越界/空输入的统一出口(返回 arr.length)。参考解法通过"排序 + 单次线性扫描"以 O(n log n) 的排序开销完成了全部 8 组断言要求的边界覆盖。若希望进一步优化,可以在此基础上研究二分查找定位插入点的写法,但那已超出本题的判题要求,属于可选项而非必要条件。题目的完整题面、断言与官方解法均可在 curriculum/challenges/english/blocks/basic-algorithm-scripting/a24c1a4622e3c05097f71d67.md 中查看。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00