news 2026/9/8 19:56:24

freeCodeCamp 每日编程挑战解析:Unique Characters —— 字符串字符唯一性判定的实现与工程化组织

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
freeCodeCamp 每日编程挑战解析:Unique Characters —— 字符串字符唯一性判定的实现与工程化组织

freeCodeCamp 每日编程挑战解析:Unique Characters —— 字符串字符唯一性判定的实现与工程化组织

【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp

本文围绕 freeCodeCamp 课程库中「每日编程挑战(Daily Coding Challenges - JavaScript)」系列的第 30 题Unique Characters展开:完整继承原题的问题描述、测试断言与参考答案,逐行剖析其解法思路与时间复杂度,并结合仓库源码说明这道挑战在 freeCodeCamp 课程体系中是如何被定义、校验与分发的。读完后,你既能独立解出这道字符唯一性判定题,也能理解 freeCodeCamp 用challengeType、Joi Schema 和块结构文件组织编程题的完整机制。

问题定义:判断字符串中所有字符是否唯一

该挑战的题目文件位于 curriculum/challenges/english/blocks/daily-coding-challenges-javascript/68af0687ef34c76c28ffa547.md,其核心描述为:

Given a string, determine if all the characters in the string are unique.

  • Uppercase and lowercase letters should be considered different characters.

即:给定一个字符串,判断其中所有字符是否互不重复;同时明确了一条边界规则——大写与小写字母视为不同字符,因此"aA"是合法的唯一字符串。需要实现一个allUnique(str)函数,返回布尔值。

完整的测试断言与期望行为

原题给出了 7 组测试用例(# --hints--段),它们同时覆盖了「唯一」与「不唯一」两类输入,以及字母、大小写混合、纯数字符号等不同字符集。这些断言是解法正确性的验收标准:

输入期望结果考察点
"abc"true最基础的唯一字符场景
"aA"true大小写视为不同字符(题目关键规则)
"QwErTy123!@"true字母、数字、符号混合且全部唯一
"~!@#$%^&*()_+"true纯特殊符号,含+_等易被转义/忽略的字符
"hello"false字母l重复
"freeCodeCamp"false大小写混用但eCo等有重复
"!@#*$%^&*()aA"false符号重复(*^%&等出现两次),而非常见的大小写陷阱

对应的断言写法为:

assert.isTrue(allUnique("abc")); assert.isTrue(allUnique("aA")); assert.isTrue(allUnique("QwErTy123!@")); assert.isTrue(allUnique("~!@#$%^&*()_+")); assert.isFalse(allUnique("hello")); assert.isFalse(allUnique("freeCodeCamp")); assert.isFalse(allUnique("!@#*$%^&*()aA"));

这 7 组用例值得注意的一点是:"!@#*$%^&*()aA"这个反例中字母部分是唯一的,重复发生在符号上,因此解法必须对任意字符(而不只是字母)做统一的重复检测,不能对字符做「归一化」或按类别分开处理。

种子代码:从模板出发

挑战提供给学习者的种子(# --seed--段)是一个空壳函数:

function allUnique(str) { return str; }

其中return str只是占位返回,学习者的任务是在函数体内实现判定逻辑并返回true/false。这种「函数签名固定 + 测试先行」的模式是整个 Daily Coding Challenges 系列的统一结构:题面给出函数名与参数,hints 段给出断言作为测试骨架。

官方参考解法逐行剖析

原题# --solutions--段给出的参考实现如下,采用的是「已见字符累积 + 提前终止」策略:

function allUnique(str) { let seen = ''; for (let char of str) { if (seen.includes(char)) { return false; } seen += char; } return true; }

逐行拆解其工作机制:

  1. let seen = '';—— 用一个字符串累积所有「尚未重复出现」的字符,充当字符登记表。
  2. for (let char of str)—— 使用for...of逐字符遍历(相对于for (let i = 0; i < str.length; i++)下标访问,for...of对含多字节字符的字符串按码点/码元迭代,更符合「逐字符」语义)。
  3. if (seen.includes(char)) { return false; }—— 一旦当前字符已在seen中出现过,立即返回false。这是关键优化:发现第一个重复字符即终止,最坏情况不需要遍历完整字符串。
  4. seen += char;—— 字符唯一时,将其追加进登记表,继续下一轮。
  5. return true;—— 遍历结束仍未发现重复,返回true。空字符串""会直接走到这一步返回true(空串中不存在重复字符,逻辑自洽)。

allUnique("aA")为例走一遍:'a'不在seen中,追加得seen = "a"'A'不在"a"中(String.prototype.includes是大小写敏感的),追加得seen = "aA";遍历结束返回true。这正好验证了题目「大小写视为不同字符」的规则——解法没有调用toLowerCase()之类的归一化,是故意为之。

再以allUnique("hello")为例:hel依次进入seen,第二个lseen.includes("l")命中,函数在遍历到第 4 个字符时就提前返回false

复杂度分析与替代写法

从源码结构看,参考解法的时间复杂度为 O(n²):每轮调用includes需要在长度为 O(n) 的seen上做一次线性扫描,共 n 轮。在 Daily Coding Challenges 这类输入较短的题设下完全够用,但将其作为算法练习,值得对比更优实现——使用Set

function allUnique(str) { const seen = new Set(); for (const char of str) { if (seen.has(char)) return false; seen.add(char); } return true; }

Set.prototype.has的平均查找为 O(1),整体降到 O(n) 时间、O(n) 空间。两种写法语义完全等价,且都保持大小写敏感。也可以进一步用new Set(str).size === str.length一行式表达,但它无法提前终止、会先物化整个集合,属于「简洁优先」的取舍;在本题的测试断言下同样全部通过。三种写法共同的前提是:不得对输入做任何大小写折叠或字符过滤,否则allUnique("aA")这类断言会失败。

这道挑战在 freeCodeCamp 仓库中如何被组织

理解了题目本身后,从仓库源码看它如何被 freeCodeCamp 的课程系统定义、校验与分发,可以补全「一道题从 Markdown 到学员编辑器」的完整链路。

challengeType 28:dailyChallengeJs 的类型注册

该挑战的 frontmatter 中写有challengeType: 28。在 packages/shared/src/config/challenge-types.ts 中可以看到类型常量表:

const dailyChallengeJs = 28; const dailyChallengePy = 29;

并据此定义了视图类型与提交方式:

[dailyChallengeJs]: 'classic', // viewTypes:使用 classic 挑战视图(编辑器 + 测试面板) [dailyChallengeJs]: 'tests', // submitTypes:提交时执行测试断言

同时该文件提供getIsDailyCodingChallenge(challengeType)getDailyCodingChallengeLanguage(challengeType)两个工具函数,后者将28映射为'javascript'29映射为'python'。客户端与 API 侧据此区分每日挑战的语言与展示逻辑(客户端相关组件位于 client/src/components/daily-coding-challenge/)。

块结构文件:挑战的排序与元信息

这道题在课程块中的位置由 curriculum/structure/blocks/daily-coding-challenges-javascript.json 声明。其中该挑战以id精确对应到题目文件:

{ "id": "68af0687ef34c76c28ffa547", "title": "Challenge 30: Unique Characters" }

challengeOrder数组的先后顺序即课程展示顺序,本文件同时声明了块的级元信息:"blockLayout": "legacy-challenge-list"(列表式导航)、"usesMultifileEditor": true"disableLoopProtectTests": true(禁用死循环保护测试,允许解法中使用循环)。可以推断,题目文件中的dashedName: challenge-30与标题Challenge 30: Unique Characters正是该块内第 30 位的编号体现。

Joi Schema:挑战文件的字段级校验

每道挑战在入库前需通过 curriculum/schema/challenge-schema.js 中 Joi Schema 的校验。与本挑战相关的约束包括:

  • challengeType: Joi.number().min(0).max(33).required()—— 类型必须是 0–33 的整数,28合法;
  • description对普通类型(含 dailyChallengeJs)为Joi.string().required(),因此题目文件必须包含# --description--段;
  • disableLoopProtectTests: Joi.boolean().required()helpCategory限定枚举值(本块使用'JavaScript')、id: Joi.objectId().required()等。

这意味着题目 Markdown 的 frontmatter(idtitlechallengeTypedashedName)与正文小节(# --description--# --hints--# --seed--# --solutions--)共同构成一份被 Schema 约束的结构化数据,而不是纯自由文本。

挑战数据的落库:daily-challenges 种子脚本

每日挑战还需要同步到后端数据库供「每日推荐」功能使用。tools/daily-challenges/README.md 描述了种子流程:复制sample.env.env、安装依赖后,在tools/daily-challenges目录执行pnpm seed-daily-challenges,脚本会从 GraphQL 拉取挑战数据,写入freecodecamp数据库的DailyCodingChallenges集合。这解释了同一道挑战在仓库中存在「静态 Markdown 题面(课程块内按序练习)」与「数据库记录(每日挑战日历分发)」两种形态的原因。

小结与自检清单

Unique Characters 这道题的完整学习闭环可以概括为:

  1. 读题:明确「全部字符唯一 + 大小写敏感」两条语义;
  2. 对照断言:用 7 组 hints 断言覆盖正例(纯字母、大小写混合、纯符号)与反例(字母重复、符号重复);
  3. 实现:先写「已见字符累积 +includes提前返回」的 O(n²) 参考解法,再尝试Set的 O(n) 写法;
  4. 验证约束:确认没有调用任何大小写折叠方法,"aA"必须返回true
  5. 追溯工程链路:从题目 Markdown → 块结构 JSON(排序/元信息)→ Joi Schema(字段校验)→challengeType 28的视图与提交映射 → 种子脚本落库,理解 freeCodeCamp 如何将一道算法题完整工程化。

完成上述五步后,你不仅解出了这道字符串处理题,也掌握了 freeCodeCamp 课程库中每日挑战从定义到运行的组织方式,可以继续按challengeOrder顺序进入 Challenge 31: Array Diff 等后续题目。

【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 19:55:26

Jetson Nano视觉伺服实战:六自由度机械臂端到端控制

简介&#xff1a;本资源是一套基于Jetson Nano平台实现视觉引导与深度学习驱动的六自由度机械臂控制系统&#xff0c;面向计算机、人工智能、电子信息等专业学生及嵌入式AI学习者&#xff0c;适用于课程设计、毕业设计与机器人视觉项目实践。压缩包共6个文件&#xff0c;含4个核…

作者头像 李华
网站建设 2026/9/8 19:52:30

pot-desktop 划词翻译:用 SnipDo 在 Windows 上实现选中即译

pot-desktop 划词翻译&#xff1a;用 SnipDo 在 Windows 上实现选中即译 【免费下载链接】pot-desktop &#x1f308;一个跨平台的划词翻译和OCR软件 | A cross-platform software for text translation and recognition. 项目地址: https://gitcode.com/GitHub_Trending/po/…

作者头像 李华
网站建设 2026/9/8 19:47:26

AI编程时代如何用SDD文档驱动开发避免代码失控

1. 为什么我在 AI 编程时代重新捡起“写文档”最近大半年&#xff0c;我几乎每天都在用各种 AI 编程工具写代码、改代码、查 bug。说实话&#xff0c;效率提升确实明显&#xff0c;但踩的坑也一点不少&#xff1a;让 AI 生成一个功能模块&#xff0c;它洋洋洒洒给你几百行代码&…

作者头像 李华