首页
/ freeCodeCamp 算法题精讲:"Where do I Belong" 查找数组插入下标的完整实现

freeCodeCamp 算法题精讲:"Where do I Belong" 查找数组插入下标的完整实现

2026-09-06 15:18:58作者:冯爽妲Honey

本篇以 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)。

这里有两个容易被忽略的隐含约定:

  1. 输入数组本身可能未排序,因此"排序"是解题的第一步,不能假设有序输入;
  2. 返回的是"最低下标",这意味着当待插入值与数组中已有元素相等时,应当返回该元素首次出现的位置(前插而非后插),这一语义由 >= 比较来保证,后文会展开。

挑战种子代码:你需要修改的起点

题目的 --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 中,idtitlechallengeTypedashedNameforumTopicId 都是受 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.jsonchallengeOrder 数组可以看到,"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")与条件判断、循环,正好具备完成"排序 + 扫描"解法的知识基础。

实现要点自查清单

写这个函数时,可以用以下清单对照自检(每条都对应至少一条判题断言):

  1. 先排序再比较——getIndexToIns([3, 10, 5], 3) 要求处理未排序输入;
  2. >= 而非 > 判断——getIndexToIns([10, 20, 30, 40, 50], 30) 要求重复值时返回最低下标 2 而非跳过;
  3. 循环走完仍有出口——getIndexToIns([2, 5, 10], 15)getIndexToIns([], 1) 都依赖 return arr.length 这一兜底返回;
  4. 比较必须是数值比较——题面示例 getIndexToIns([1,2,3,4], 1.5) 说明待插入值可能是小数,排序比较函数 (a, b) => a - b 与线性比较都应保持数值语义;
  5. 返回值是数字——任何提前 return 或遗漏的分支都不能返回 undefined,否则 assert.isNumber 会失败。

小结

"Where do I Belong" 题面简短,但它把三个常见工程点浓缩在了一道题里:输入无序时的预处理(数值排序)、相等值的"前插"语义(>= 比较)、以及越界/空输入的统一出口(返回 arr.length)。参考解法通过"排序 + 单次线性扫描"以 O(n log n) 的排序开销完成了全部 8 组断言要求的边界覆盖。若希望进一步优化,可以在此基础上研究二分查找定位插入点的写法,但那已超出本题的判题要求,属于可选项而非必要条件。题目的完整题面、断言与官方解法均可在 curriculum/challenges/english/blocks/basic-algorithm-scripting/a24c1a4622e3c05097f71d67.md 中查看。

登录后查看全文
热门项目推荐
相关项目推荐