首页
/ freeCodeCamp 每日编程挑战第 149 题 vOwElcAsE 解题指南:字符串元音大写化算法

freeCodeCamp 每日编程挑战第 149 题 vOwElcAsE 解题指南:字符串元音大写化算法

2026-09-08 16:12:46作者:俞予舒Fleming

本指南以 freeCodeCamp 课程仓库中 daily-coding-challenges-javascript 板块的 Challenge 149 为题,完整讲解题目要求、5 组断言测试的边界语义、官方参考解法的逐行原理与复杂度分析,并给出多行 replacemap 等替代实现与本地自测方法。读者通过本文可掌握一类"逐字符分类重写字符串"问题的通用分析套路,也能了解这类题库文件在 freeCodeCamp 课程体系中的组织方式与校验规则。

题目定位:它在仓库中的哪个位置

该题是 freeCodeCamp 课程 daily-coding-challenges-javascript 板块(每日编程挑战 JavaScript 系列)中编号连续的每日一题。原始挑战文件位于:

每个挑战文件头部都有一组 YAML frontmatter,其中 challengeType: 28 是关键元数据。结合 packages/shared/src/config/challenge-types.ts 中的类型定义(第 30 行 const dailyChallengeJs = 28;)可以确认,类型 28 代表"JavaScript 每日编程挑战"(Daily Coding Challenge - JavaScript),这一类型在该文件的映射表中对应 runner 'tests',也就是说它走的是标准测试运行器路径,由断言逐条判定正确性。

题目的注册信息记录在板块结构文件 curriculum/structure/blocks/daily-coding-challenges-javascript.jsonchallengeOrder 列表中:id 为 69306364df283fcaff2e1ada,标题为 "Challenge 149: vOwElcAsE"。同一板块还收录了编号相邻的 Challenge 144(Resolution Streak)到 Challenge 155(Plant the Crop)等题目。所有课程 Markdown 文件在入库前都需通过 curriculum/schema/challenge-schema.js 的 Schema 校验,其中 challengeType: Joi.number().min(0).max(33).required() 即对应 frontmatter 中 28 的取值合法性。

题目描述与三条核心规则

原题描述如下:

Given a string, return a new string where all vowels are converted to uppercase and all other alphabetical characters are converted to lowercase.

翻译过来即:给定一个字符串,返回一个新字符串——所有元音字母转大写,其余字母转小写,非字母字符保持不变。

描述下方附带两条约束细则,它们是所有边界情况的判据来源:

  • 元音定义为 "a""e""i""o""u"不分大小写(即 AEIOU 也属于元音,处理时应按元音处理)。
  • 非字母字符保持不变(数字、标点、空格、波浪号等一律原样输出)。

三句话合起来,其实定义了输出规则的三分支逻辑

字符类别 处理动作
元音字母(a/e/i/o/u 及其大写形式) 转为大写
其他字母(辅音字母,大小写皆可) 转为小写
非字母字符(数字、空格、标点等) 原样保留

注意第二类与第三类的区别是解题关键:"HELLO" 中的 EO 是元音,需要大写;但输入已经是大写的 HL,它们属于"其他字母",必须被小写化,因此 "HELLO" 经过转换会变成 "hEllO" 而不是保持 "HELLO"

测试用例解读:hints 定义的验收标准

原文件的 # --hints-- 区块给出了 5 组断言,全部使用 assert.equal 进行严格返回值比较。它们是该题通过的充分必要条件,也恰好覆盖了上面三类分支:

assert.equal(vowelCase("vowelcase"), "vOwElcAsE");
assert.equal(vowelCase("coding is fun"), "cOdIng Is fUn");
assert.equal(vowelCase("HELLO, world!"), "hEllO, wOrld!");
assert.equal(vowelCase("git cherry-pick"), "gIt chErry-pIck");
assert.equal(vowelCase("HEAD~1"), "hEAd~1");

逐一拆解这些用例的考察点:

输入 预期输出 主要考察点
"vowelcase" "vOwElcAsE" 全小写输入:元音上挑为大写、辅音保持小写
"coding is fun" "cOdIng Is fUn" 含空格:空格原样保留,单词内元音独立大写
"HELLO, world!" "hEllO, wOrld!" 全大写输入:元音保持大写、辅音被降为小写,逗号与感叹号不动
"git cherry-pick" "gIt chErry-pIck" 含连字符 -:非字母字符保留,且不影响两侧字符的独立判断
"HEAD~1" "hEAd~1" 混合数字 1 与波浪号 ~:两者原样保留,数字不参与字母判断

从第 3、5 两个用例可以提炼出最容易踩的坑:大小写混合输入中,辅音字母一律要小写,即便输入原本是大写;同时输出长度必须与输入完全一致(因为没有任何字符被删除或新增,只有大小写变化),任何丢失字符或拆分不当的实现都会立刻导致断言失败。

种子代码:需要补全的函数骨架

原文件的 # --seed-contents-- 给出如下起始代码:

function vowelCase(str) {

  return str;
}

函数接收一个字符串参数 str,当前是"原样返回"的空实现。要全部通过上面的 5 条断言,你需要在不改变函数签名与返回值形态的前提下,补全中间逻辑。由于函数的输入、输出都是单个字符串,且不产生任何副作用,这是一个典型的纯函数题目,非常便于单元测试。

官方解法逐行解读

原文件 # --solutions-- 区块给出的参考实现如下:

function vowelCase(str) {
  const vowels = "aeiouAEIOU";
  let result = "";

  for (let char of str) {
    if (vowels.includes(char)) {
      result += char.toUpperCase();
    } else if (/[a-zA-Z]/.test(char)) {
      result += char.toLowerCase();
    } else {
      result += char;
    }
  }

  return result;
}

逐行分析其设计思路:

  1. const vowels = "aeiouAEIOU";:用一个包含 10 个字符的字符串作为元音查找表。同时收录大小写两套,是为了让后续的 includes 判断无需额外的大小写归一化调用。
  2. let result = "";:累积结果的字符串变量。采用"扫描 + 拼接"的方式逐字符构建输出,保证输出与输入逐位置一一对应。
  3. for (let char of str)for...of 会按码点顺序遍历字符串的每个字符,天然适合逐字符重写类问题。
  4. 第一层判断 vowels.includes(char):直接命中"是否是元音"这个最高优先级条件。命中则执行 char.toUpperCase()。注意:由于查找表同时包含大小写,这里即使原字符是大写元音,toUpperCase() 也只是幂等地保持大写,不会出错。
  5. 第二层判断 /[a-zA-Z]/.test(char):正则字面量匹配任意单个英文字母。走到这一分支说明字符是辅音(已排除元音且是字母),因此统一执行 char.toLowerCase() 降为小写。
  6. else 兜底:既非元音也非字母的字符(数字、空格、标点、连字符、波浪号等)直接原样追加。
  7. return result;:返回累积的新字符串,原字符串 str 完全不被修改。

"HELLO, world!" 代入走一遍:HLL 落入第二个分支转小写;EO 落入第一个分支(保持)大写;空格与 ,! 进入 else 原样保留,最终得到 "hEllO, wOrld!",与断言一致。

时间复杂度与空间复杂度

  • 时间复杂度 O(n):单次线性遍历,每个字符只做常数次字符串查找(includes 对 10 字符表为常数级)与一次正则测试,n 为输入字符串长度。
  • 空间复杂度 O(n)result 字符串的长度与输入一致(字符一一对应、长度不变),因此需要与输入等长的额外空间。

若追求"原地、零额外空间"则不可行——JavaScript 字符串不可变,重写结果必然要落到新字符串中,因此 O(n) 空间是本类题目不可避免的下界。

等价替代实现:正则替换与数组化写法

除官方逐字符遍历写法外,还有两种常见等价实现,核心差异只在代码组织方式,判定逻辑(元音大写、辅音小写、其余不动)完全一致。

写法一:利用 String.prototype.replace + 回调函数。 先用字符类 [a-zA-Z] 匹配所有字母,再在回调内用不区分大小写的正则 /i 判定元音:

function vowelCase(str) {
  return str.replace(/[a-zA-Z]/g, char =>
    /[aeiou]/i.test(char) ? char.toUpperCase() : char.toLowerCase()
  );
}

这种写法把"只处理字母、其余不动"浓缩进了一个正则,非字母字符天然不被回调触碰,语义非常清晰。

写法二:先拆分再逐字符映射,最后重组。

function vowelCase(str) {
  const vowels = new Set("aeiouAEIOU");

  return [...str]
    .map(char =>
      vowels.has(char)
        ? char.toUpperCase()
        : /[a-zA-Z]/.test(char)
          ? char.toLowerCase()
          : char
    )
    .join("");
}

其中用 Set 替代字符串做 has 查询,使元音判断的语义更直白("是否属于集合"),在字符较多场景下查找也更稳定;[...str] 展开后再 .join("") 保证字符顺序不被打乱。三种实现都可以通过全部 5 条断言,选哪种主要取决于你更熟悉 for...of、正则回调还是数组管道风格。

本地自测:不依赖网页即可验证

该题的 5 条断言用 assert.equal 编写,而 assert.equal 在 freeCodeCamp 的测试运行环境中可直接使用。想在本地快速验证自己的实现,可以把它替换成 Node.js 内置的 assert.strictEqual 手动跑一遍,例如:

const assert = require("node:assert");

function vowelCase(str) {
  // ... 你的实现(可以是上方任一版本)...
}

assert.strictEqual(vowelCase("vowelcase"), "vOwElcAsE");
assert.strictEqual(vowelCase("coding is fun"), "cOdIng Is fUn");
assert.strictEqual(vowelCase("HELLO, world!"), "hEllO, wOrld!");
assert.strictEqual(vowelCase("git cherry-pick"), "gIt chErry-pIck");
assert.strictEqual(vowelCase("HEAD~1"), "hEAd~1");

console.log("all assertions passed");

将该文件保存后以 node <文件名> 运行,若控制台输出 all assertions passed,说明你的实现与官方验收标准完全一致。你还可以自行补充更刁钻的边界用例,例如空字符串 vowelCase("") 应返回 ""、纯数字串 vowelCase("12345") 应原样返回、含非英文拉丁字符的字符串等,来检验函数对"非字母一律保留"这一约束的健壮性。

这类挑战在课程与产品中的落地方式

理解题目本身之外,了解它所属的课程体系能帮助你判断这类文件的真实运行环境。从仓库证据看,该题所在的板块有如下特点:

  • 板块元信息:在 curriculum/structure/blocks/daily-coding-challenges-javascript.json 中,该板块声明了 helpCategory: "JavaScript"blockLayout: "legacy-challenge-list",并针对每日题的特性开启了 usesMultifileEditordisableLoopProtectTests(关闭循环保护测试,便于处理纯算法实现)。
  • 文件规范约束:题目 Markdown 文件遵循 # --description--# --hints--# --seed--(内含 ## --seed-contents--)→ # --solutions-- 的统一章节结构,这些区块正是 freeCodeCamp 课程流水线(解析、Schema 校验、测试注入)的输入约定。
  • 类型驱动的渲染与测试:客户端侧通过 packages/shared/src/config/challenge-types.ts 提供的 getIsDailyCodingChallengegetDailyCodingChallengeLanguage 等工具识别类型 28(JavaScript)与 29(Python)的每日题,并将其路由到 tests 类测试运行器执行断言(参见 client/src/templates/Challenges/utils/build.tsrunnerTypes[challengeType] 的解析);课程 Schema 校验器 curriculum/schema/challenge-schema.js 则对所有字段(含 challengeTypedescriptionhintsseedsolutions)的合法性进行把关。

小结

Challenge 149 "vOwElcAsE" 表面上是简单的大小写转换,实则精准考察了三类字符分支的优先级处理:元音优先大写、辅音一律小写、非字母字符原样保留,且元音判定不分大小写。官方解法以 for...of 线性扫描配合查找表与字母正则,实现了 O(n) 时间、O(n) 空间的纯函数;文中的正则回调版与数组管道版则是同样语义的不同表达。配合 5 组断言理解边界、再以 Node 本地自测验证,即可完整吃透这道典型的字符串重写题,并为同一板块中大量同构的逐字符处理题目(如 Challenge 82 SpOoKy~CaSe、Challenge 163 Consonant Case)打下基础。

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

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.16 K
2.78 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
904
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
932
1.86 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
862
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.95 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.38 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
535
606
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
549
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23