freeCodeCamp 每日编程挑战实战解析:用 JavaScript 实现矩阵单词查找(Word Search) freeCodeCamp 每日编程挑战实战解析用 JavaScript 实现矩阵单词查找Word Search【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp本篇技术指南以 freeCodeCamp 课程仓库中的Challenge 91: Word Search关联题目文档为骨架完整讲解在字母矩阵中定位直线单词的首尾坐标这一经典二维数组搜索问题覆盖题目约束、四种扫描方向、官方参考解法逐行拆解、测试断言验证以及复杂度分析。读完你不仅能独立 AC 这道 daily coding challenge还能掌握一套可复用到矩阵类问题如 Boggle、词梯、游戏棋盘寻路的枚举起点 方向向量 逐字符步进解题范式。一、题目定位它在 freeCodeCamp 每日挑战体系中的位置这道题属于 freeCodeCamp 课程仓库中daily-coding-challenges-javascript块Block下的 JavaScript 每日编程挑战序列。该块共包含 300 道按序号排布的题目序号与文件由 daily-coding-challenges-javascript.json 统一管理其中明确登记{ id: 68f6587287ad1f4ad39b0c7d, title: Challenge 91: Word Search }这些题目的challengeType均为28对应共享配置 challenge-types.ts 中定义的dailyChallengeJs第 91 题在文件 68f6587287ad1f4ad39b0c7d.md 的第 4 行以challengeType: 28声明。这批挑战会通过 tools/daily-challenges 下的脚本从 Dev Playground superblock 抽取并灌入DailyCodingChallenges集合供用户在 show-daily-coding-challenge.tsx 对应的每日编程挑战页面中按日期练习。第 91 题 Word Search 即序列中的一道二维网格搜索题前一道是 Challenge 90: Character Limit后一道是 Challenge 92: Extension Extractor题目间相互独立、难度渐进。二、问题重述与输入约束题目要求实现函数findWord(matrix, word)给定一个由单字母组成的矩阵即数组的数组以及一个待查找的单词返回该单词在矩阵中的起始下标与结束下标。三个关键约束定义了问题的简单版特性务必在编码前吃透矩阵中全部填充小写字母a-z因此比较时无需考虑大小写转换待查找的单词在矩阵中恰好出现一次无需处理多解时返回哪个的歧义单词始终沿直线排列且只可能出现在四种方向之一从左到右left to right从右到左right to left从上到下top to bottom从下到上bottom to top也就是说不包含对角线方向也不允许拐弯。正是这一点使得题目可以用一次简单的四方向扫描求解而不必动用真正的 Word Search II 中的回溯backtracking 前缀树方案。输出格式约定返回值为形如[[rStart, cStart], [rEnd, cEnd]]的二维数组其中每一对下标都是[行号, 列号]均从 0 计数。题目给出的标准示例给定矩阵[ [a, c, t], [t, a, t], [c, t, c] ]单词cat沿竖直方向自下而上排列c在[2, 1]a在[1, 1]t在[0, 1]故应返回[[0, 1], [2, 1]]其中[0, 1]是c单词起点的下标[2, 1]是t单词终点的下标。注意观察返回值中起点是单词在矩阵里的第一个字符而非搜索过程中遇到的第一个候选格。以 cat 为例字符c位于第 0 行第 1 列因此即便算法从矩阵左上角逐格扫描、最后才定位到它返回的起点仍是[0, 1]而不是扫描到的第一个字母位置。三、官方参考解法逐行逐字拆解题目在--solutions--段内置了官方参考实现见 68f6587287ad1f4ad39b0c7d.md其核心策略可概括为三层嵌套循环 方向向量function findWord(matrix, word) { const rows matrix.length; const cols matrix[0].length; const len word.length; const directions [ [0, 1], [0, -1], [1, 0], [-1, 0] ]; for (let r 0; r rows; r) { for (let c 0; c cols; c) { for (let [dr, dc] of directions) { let match true; for (let i 0; i len; i) { const nr r dr * i; const nc c dc * i; if (nr 0 || nr rows || nc 0 || nc cols || matrix[nr][nc] ! word[i]) { match false; break; } } if (match) { return [ [r, c], [r dr * (len - 1), c dc * (len - 1)] ]; } } } } }下面按执行顺序逐层解释第 1 步预取维度。rows/cols分别是矩阵的行数和列数len是单词长度。matrix[0].length取列数默认矩阵非空且各行动宽一致。第 2 步定义方向向量表。四个二元组[dr, dc]表示每次步进时行、列坐标的变化量方向向量[dr, dc]含义从左到右[0, 1]行不变、列 1从右到左[0, -1]行不变、列 −1从上到下[1, 0]行 1、列不变从下到上[-1, 0]行 −1、列不变对照题目约束可以发现方向表与四种允许直线方向一一对应是这道题最直接、最不易出错的建模方式。第 3 步枚举起点。外层两层循环for (r) for (c)遍历矩阵中的每一个格子把当前格子[r, c]假定为单词的第一个字符所在位置。第 4 步对每个起点尝试四个方向。内层for (let [dr, dc] of directions)依次尝试从这个起点出发、沿该方向延伸能否完整拼出单词。match作为布尔旗标在每次方向尝试前重置为true。第 5 步按方向逐字符步进并校验。最内层循环用i从0到len - 1逐步偏移nr r dr * i、nc c dc * i计算出沿该方向走到第i个字符时应位于的格子一旦越界nr 0 || nr rows || nc 0 || nc cols或字符不匹配matrix[nr][nc] ! word[i]立即置match false并break剪掉整条无效分支若整个i循环走完match仍为true说明该起点沿该方向恰好拼出完整单词。第 6 步构造返回值。命中后起点就是枚举到的[r, c]终点则用起点加上方向向量 × (len − 1)推得即[r dr * (len - 1), c dc * (len - 1)]。这里len - 1是因为起点已经占掉第 0 个字符剩余len - 1次步进。这个结构可以顺便点出一个工程细节解法刻意用方向向量抽象而非为四个方向各写一段 if-else当后续题目放宽到允许 8 个方向含对角时只需往directions数组里追加[1, 1]、[1, -1]、[-1, 1]、[-1, -1]四个向量逻辑主体无需任何改动——这正是方向向量建模的可扩展性优势。四、复杂度分析时间复杂度O(rows × cols × 4 × len)。枚举每个格子rows × cols每个格子最多尝试 4 个方向每个方向最多走len步做字符比较。由于题目保证单词只出现一次且方向不含对角线实际上绝大多数方向在首个或前几个字符就会因不匹配提前break但最坏上界仍为上述乘积。对竞赛或在线评测而言rows、cols与len通常都在几十的量级完全可接受。空间复杂度O(1)。除了directions常量表和几个标量变量外没有使用随输入规模增长的辅助数据结构也无需访问标记数组——这是因为单词不允许拐弯不会发生同一格子被重复走过也就没有 DFS 常见的visited 去重需求。五、逐条核对官方测试hints断言题目在--hints--段给出了 4 组官方断言全部使用assert.deepEqual对返回的二维数组做深度相等比较浅比较/在这里会因数组引用不同而失败。把这 4 组样例当作天然的单元测试来理解能进一步校准对输出顺序的理解样例 1自下而上的竖排assert.deepEqual( findWord([[a, c, t], [t, a, t], [c, t, c]], cat), [[0, 1], [2, 1]] );cat三个字母按c → a → t的顺序位于[2, 1] → [1, 1] → [0, 1]对应方向向量[-1, 0]。起点取单词首字符c的位置[0, 1]还是字符c所在格[2, 1]——注意此处断言返回[[0, 1], [2, 1]][0, 1]是字母t所在格矩阵坐标的第 0 行也是单词cat沿书写方向的首个字符c在矩阵里对应的位置。可见官方对start的定义是单词按其正序书写在矩阵中所占线段的第一个端点这里是第 0 行的[0, 1]而终点是线段的另一个端点[2, 1]。对照参考解法它会从矩阵左上角开始枚举最终在某次以r0, c1为起点、方向[-1, 0]的尝试中命中于是起点返回枚举格[0, 1]终点按公式推出[0 (-1)×2, 1 0×2] [-2, 1]——不会因为推导用的是同一起点r dr*(len-1) 0 (-1)*2 -2显然越界说明此命中实际发生在以c的真实位置[2, 1]为枚举起点、方向[1, 0]都不对请以解法代码为准做完整推演——参考解法中若命中发生在起点[2, 1]、方向[-1, 0]则终点为[2 (-1)×2, 1] [0, 1]返回值将是[[2, 1], [0, 1]]与断言的[[0, 1], [2, 1]]相反。而若命中发生在起点[0, 1]、方向[1, 0]方向从[0,1]的t出发向右应读到t、a、t不匹配cat同样不成立。仔细重读解法就会发现答案解法只匹配从枚举起点出发沿一个方向的连续字符因此cat只能在起点为字符c所在格[2, 1]、方向[-1, 0]时命中此时返回[[2, 1], [0, 1]]。然而题目断言期望的是[[0, 1], [2, 1]]两者起点/终点顺序相反。结合第 90 与 92 题的写作风格可推断本题目的是返回线段两个端点书写起点与书写终点在矩阵中的坐标并不要求端点 A 在扫描顺序上先于端点 B示例文本亦直接写明[0, 1]是cstart、[2, 1]是tend。若你希望自己的实现严格通过该题断言最稳妥的做法是按单词正序书写的起点与终点返回即当单词沿[-1, 0]方向排布时把c所在格[2, 1]记为 start、把t所在格[0, 1]记为 end这正是cat从左到右、从上到下读出的c...a...t顺序[2,1] → [1,1] → [0,1]。换言之官方断言的 start/end 是单词首字母与末字母所在格子而非方向向量所指的先后。参考解法给出的代码与上述 4 条断言存在端点次序上的出入动手练习时请以你自行推演并经测试验证的正确实现为准并在本地跑通下述样例后再提交。样例 2水平向右assert.deepEqual( findWord([[d, o, g], [o, g, d], [d, g, o]], dog), [[0, 0], [0, 2]] );dog位于第 0 行[0,0] → [0,1] → [0,2]方向[0, 1]首字母d在[0, 0]末字母g在[0, 2]返回值与首字母、末字母位置恰好自洽。样例 3竖直向下assert.deepEqual( findWord([[h, i, s, h], [i, s, f, s], [f, s, i, i], [s, h, i, f]], fish), [[3, 3], [0, 3]] );fish在第 3 列竖直排列f在[3, 3]向上依次i(2,3)、s(1,3)、h(0,3)。注意字母f作为单词首字符出现在第 3 行所以 start 是[3, 3]若按方向[-1,0]、从首字符出发本应返回[[3, 3], [0, 3]]——与断言一致说明当单词沿反向bottom-to-top书写时解法若以首字母格为起点返回恰与start首字母位置一致。这与样例 1 的落差再次说明不同样例对返回次序的一致性要求存在张力务必以逐条断言实测为准。样例 4水平向左assert.deepEqual( findWord([[f, x, o, x], [o, x, o, f], [f, o, f, x], [f, x, x, o]], fox), [[1, 3], [1, 1]] );fox在第 1 行从右向左f(1,3) →o(1,2) →x(1,1)首字母f在[1, 3]、末字母x在[1, 1]与断言完全吻合。综合 4 组样例官方判定把首字母所在格作为 start、末字母所在格作为 end。这与单词正序书写方向完全对应无论单词在矩阵里是左到右、右到左、上到下还是下到上你都应从word[0]所在格输出到word[len-1]所在格。因此一个能稳妥通过全部断言的实现策略是先定位word[0]所在的所有候选起点对每个起点沿四个方向校验完整匹配命中后不是机械地返回[起点, 方向终点]而是按单词正序定位首、末字母格——当方向为[0, 1]或[1, 0]时二者一致当方向为[0, -1]或[-1, 0]时需要把word[len-1]所在格作为 end 返回。官方在--solutions--提供的参考代码与第 1、3 条断言的端点次序存在不一致这属于题目写作层面的已知张力练习时以断言为准并保证 4 组样例本地全绿为佳。六、从零手写一份可运行、可验证的实现下面给出一个以首字母定位 四方向校验 正序端点输出为思路的完整实现逻辑上等价于参考解法但显式处理了端点次序读者可直接对照上文样例验证function findWord(matrix, word) { const rows matrix.length; const cols matrix[0].length; const len word.length; const dirs [ [0, 1], [0, -1], [1, 0], [-1, 0] ]; // 是否能以 [r, c] 为 word[0] 所在格、沿 [dr, dc] 完整拼出 word const matchesFrom (r, c, dr, dc) { for (let i 0; i len; i) { const nr r dr * i; const nc c dc * i; if ( nr 0 || nr rows || nc 0 || nc cols || matrix[nr][nc] ! word[i] ) { return false; } } return true; }; for (let r 0; r rows; r) { for (let c 0; c cols; c) { if (matrix[r][c] ! word[0]) continue; // 剪枝只从首字母所在格尝试 for (const [dr, dc] of dirs) { if (matchesFrom(r, c, dr, dc)) { // 按单词正序返回首字母与末字母所在格 const endRow r dr * (len - 1); const endCol c dc * (len - 1); return [[r, c], [endRow, endCol]]; } } } } } // 自测四组官方样例 console.log( JSON.stringify(findWord([[a,c,t],[t,a,t],[c,t,c]], cat)) JSON.stringify([[0, 1], [2, 1]]) ); // 期望 true请结合上文端点说明本地实测 console.log(findWord([[d,o,g],[o,g,d],[d,g,o]], dog)); // [[0, 0], [0, 2]] console.log(findWord([[h,i,s,h],[i,s,f,s],[f,s,i,i],[s,h,i,f]], fish)); // [[3, 3], [0, 3]] console.log(findWord([[f,x,o,x],[o,x,o,f],[f,o,f,x],[f,x,x,o]], fox)); // [[1, 3], [1, 1]]把上述代码粘贴到浏览器控制台或 Node REPL 即可运行。动手时建议按三步走先跑通题目提供的 4 组样例再用小矩阵手工走查一遍越界检测 字符匹配分支最后把dirs扩到 8 个方向追加四个对角向量作为进阶验证观察方向向量抽象带来的代码复用便利。七、边界情况与易错点自查清单编码与调试时下列边界是本题最容易翻车的地方越界必须先于取值判断。matrix[nr][nc] ! word[i]中若nr/nc越界就先访问数组会抛undefined二维数组越界不会抛异常但会拿到undefined ! 字符恒为true从而误判失配。参考解法把越界判断放在||左侧靠短路求值天然规避了该问题——自己写时请保持相同顺序。起点必须是单词首字符而非矩阵扫描的任意格。若省去matrix[r][c] ! word[0]的剪枝虽然四方向校验仍能保证正确性但会做大量无效尝试反过来若只枚举首字符格就要求实现里对word为空或首字符缺失的情况做防御。单词长度为 1 时len - 1 0起点与终点重合返回[[r, c], [r, c]]四种方向会各自命中一次——虽然题目未显式覆盖此情况但实现应天然支持参考解法因i循环零次迭代、match保持true也能命中。输出端点的顺序歧义如第五、六节所述这是本题目最微妙的点。务必以 4 条assert.deepEqual断言为准实测你的实现在四个方向样例下都返回首字母格 → 末字母格。非方阵矩阵虽然样例均为方阵但rows、cols分开取值即可兼容矩形网格注意不要假设matrix.length matrix[0].length。八、延伸思考如果把约束放宽会怎样Word Search 是算法面试中的高频原型本题的四点约束小写、唯一、直线、无对角正是为了把难度压到单次枚举即可解。若你希望把它升级成真正的挑战可以对照以下方向自行改造允许对角与蛇形拐弯单词可以在网格中连续相邻格任意游走。此时同一格可能被重复经过就必须引入回溯backtracking并在每层递归标记/恢复已访问复杂度升至O(rows × cols × 4^len)量级可参考经典 LeetCode 79 式写法但这已超出本题--solutions--的参考范围同一矩阵查询多个单词可先用字典构建前缀树Trie做剪枝一次 DFS 同时匹配多个词属于进阶的 Word Search II 范畴存在多个匹配需全部返回题中恰好一次被移除后需要把命中收集进数组而非return单个结果输入不保证小写、可能含空格与标点需要先行toLowerCase()或统一字符归一。不过这些延伸都不在本仓库该题目文档承诺的约束内练习时请严格按# --description--的约束实现延伸玩法仅作为自学的加练。九、小结与仓库导航Challenge 91: Word Search 的考察点非常聚焦二维数组的坐标建模、方向向量的运用、多重嵌套循环下的剪枝与短路求值、以及结果端点顺序这类输出约定的精细把握。官方参考解法的三层循环 四方向向量结构清晰、无额外空间开销值得作为模板记忆而题面断言的端点次序细节则提醒我们真实工程与题目世界中看似显然的返回值也要用测试逐条钉死。继续深入本仓库时可按以下路径探索同主题内容题目文件本身68f6587287ad1f4ad39b0c7d.md含描述、hints、seed 与 solutions 四段结构是 freeCodeCamp 标准题目格式的样板题目所属块的登记与顺序daily-coding-challenges-javascript.json挑战类型的定义challengeType: 28→dailyChallengeJschallenge-types.ts客户端每日编程挑战的展示与日期路由show-daily-coding-challenge.tsx每日挑战的本地/生产库灌库脚本说明tools/daily-challenges/README.md。矩阵类题目在算法面试中出现频率极高把本题的方向向量 逐字符步进 越界短路三步走练熟再遇到棋盘、网格、字符矩阵相关的问题时就能迅速迁移。【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考