news 2026/9/10 11:48:15

freeCodeCamp JavaScript 每日挑战 349:Cell Signal——用三个信号塔坐标在手机网格上定位

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
freeCodeCamp JavaScript 每日挑战 349:Cell Signal——用三个信号塔坐标在手机网格上定位

freeCodeCamp JavaScript 每日挑战 349:Cell Signal——用三个信号塔坐标在手机网格上定位

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

本篇指南完整讲解 freeCodeCamp 课程中 JavaScript 每日编码挑战(Daily Coding Challenges)第 349 题 “Cell Signal”:给定一个包含三个信号塔读数的网格,如何判定手机所在单元格。你将完整继承原题的题面规则、全部 5 个测试用例与参考实现,并进一步结合仓库中的区块配置文件、挑战类型定义与挑战页面源码,理解这道题在 freeCodeCamp 课程体系中的实际运行形态,最终掌握“约束交集 + 方向距离判定”这一类网格定位题的通用解法。

题目背景:网格中的手机定位

该题目来自 freeCodeCamp 课程仓库的 JavaScript 每日挑战区块,挑战文件为 6a22d77ddf034bc4e35b1d56.md。在区块结构文件 daily-coding-challenges-javascript.json 的challengeOrder中,它被排定为 “Challenge 349: Cell Signal”(相邻题依次为 Challenge 348: Loan Calculator 与 Challenge 350: Letter Distance)。

题面原文如下:

Given a grid containing three cell tower readings, determine the location of the phone.

  • Each cell in the grid is either0(no tower) or a positive integer representing the number of cells to the phone, measured in a straight line: horizontal, vertical, or diagonal.
  • Return the[row, col]of the cell that is the correct number of cells from all three towers.
  • There is always exactly one solution.

翻译成中文:

  1. 网格中每个单元格要么是0(无信号塔),要么是一个正整数,表示该信号塔到手机的直线格数——这个直线只能是水平、垂直或 45° 对角线方向;
  2. 要求返回[row, col](行、列),该单元格必须同时满足全部三个信号塔的距离约束;
  3. 题目保证有且只有一个解,因此找到第一个满足条件的单元格即可返回。

题目的输入输出契约是:

function findSignal(grid) { // grid: 二维整数数组 // 返回值: [row, col] 或 undefined(题面保证一定有解) }

初始脚手架(seed)代码如下,读者需要在函数体内完成实现:

function findSignal(grid) { return grid; }

核心数学:什么叫“直线距离为 dist”

这是本题唯一的难点,也是最容易写错的地方。所谓“手机在信号塔的dist格之外”,指的是二者位于同一条水平线、同一条垂直线或同一条对角线上,且相隔恰好dist格。

设候选手机位置为(r, c),信号塔位于(tr, tc),记行差dr = |r - tr|、列差dc = |c - tc|,则合法位置等价于以下三种情形之一:

方向条件
垂直方向(同一列)dc === 0dr === dist
水平方向(同一行)dr === 0dc === dist
对角线方向(45°)dr === dc === dist

注意两点:

  • 距离不是欧氏距离,也不是曼哈顿距离,而是轴对齐或对角线上的格数
  • dist至少为 1(正整数),所以手机不可能恰好落在信号塔格子上——这也意味着所有非零格子都可以直接从候选集中排除。

参考实现用一个布尔函数把上表压缩成一行判定,等价地写成了:

(dr === dist || dc === dist) && (dr === 0 || dc === 0 || dr === dc)

拆解来看:前一半(dr === dist || dc === dist)要求至少一个轴方向的差恰好等于dist;后一半(dr === 0 || dc === 0 || dr === dc)要求另一个轴差为 0(水平/垂直)或者两轴差相等(对角线)。两者合起来恰好覆盖且仅覆盖上表的三种合法方向,其余所有位置(例如 L 形偏移)都会被排除。

完整测试用例(原题 5 组断言)

原题提供了 5 组测试,全部采用assert.deepEqual深比较(比较的是[row, col]的精确数值与顺序)。以下 5 组用例与原题文档逐字对应,可直接复制到本地 Node 环境自测:

assert.deepEqual(findSignal([[0, 0, 1], [0, 1, 0], [0, 0, 1]]), [1, 2]);

第一个用例中,塔[1,2]的读数为1。答案[1, 2]距塔[0,2](值 1)垂直 1 格,距塔[1,1](值 1)水平 1 格,距塔[2,2](值 1)垂直 1 格,三个约束同时成立。

assert.deepEqual(findSignal([[0, 2, 0], [1, 0, 0], [0, 0, 1]]), [2, 1]);

答案[2,1]:距塔[0,1](值 2)垂直 2 格;距塔[1,0](值 1)对角线 1 格(dr=1, dc=1);距塔[2,2](值 1)水平 1 格。这组用例特意验证了对角线方向的判定。

assert.deepEqual(findSignal([[0, 0, 2, 0], [0, 0, 0, 0], [2, 0, 0, 0], [0, 0, 0, 1]]), [2, 2]);

答案[2,2]:距塔[0,2](值 2)垂直 2 格;距塔[2,0](值 2)水平 2 格;距塔[3,3](值 1)对角线 1 格。

assert.deepEqual(findSignal([[0, 3, 0, 0, 0], [0, 0, 0, 0, 2], [0, 0, 0, 0, 0], [4, 0, 0, 0, 0], [0, 0, 0, 0, 0]]), [3, 4]);

答案[3,4]:距塔[0,1](值 3)对角线 3 格(dr=3, dc=3);距塔[1,4](值 2)垂直 2 格;距塔[3,0](值 4)水平 4 格。

assert.deepEqual(findSignal([[3, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 2, 0, 0, 0, 2]]), [3, 3]);

答案[3,3]:距塔[0,0](值 3)对角线 3 格;距塔[5,1](值 2)对角线 2 格(dr=2, dc=2);距塔[5,5](值 2)对角线 2 格(dr=2, dc=2)。这是 6×6 网格,三塔两塔共用同一答案,验证了多塔交叉约束收敛到唯一解的情形。

参考实现逐段解析

原题给出的参考解法(原文档# --solutions--部分)完整如下,随后逐段分析:

function findSignal(grid) { const rows = grid.length; const cols = grid[0].length; const towers = []; for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { if (grid[r][c] !== 0) towers.push([r, c, grid[r][c]]); } } function isValid(r, c, tr, tc, dist) { const dr = Math.abs(r - tr); const dc = Math.abs(c - tc); return (dr === dist || dc === dist) && (dr === 0 || dc === 0 || dr === dc); } for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { if (grid[r][c] !== 0) continue; if (towers.every(([tr, tc, dist]) => isValid(r, c, tr, tc, dist))) { return [r, c]; } } } }

第一步:预提取信号塔。外层遍历一次网格,把所有非零格子收进towers数组,每个元素是[行, 列, 距离读数]三元组。题目保证恰好三个塔,但代码并不写死为 3——towers.every(...)对任意数量的塔都成立,实现上更具鲁棒性。

第二步:单塔判定函数isValid如上节分析,它只负责回答“候选点是否恰好位于该塔的水平/垂直/对角线dist格处”。把它抽成函数后,主循环只需对每个候选点调用every做全量约束检查。

第三步:扫描候选点并早退。第二个双重循环遍历所有格子:

  • if (grid[r][c] !== 0) continue;跳过信号塔格子——因为读数是正整数,手机不可能与塔同格,这个跳过是正确性要求而非单纯的性能优化;
  • towers.every(...)要求候选点同时满足全部塔的约束(约束交集),一旦命中就立即return [r, c]。由于题面保证有且仅有一个解,扫描到第一个合法点即全局解。

复杂度:设网格为rows × cols、塔数为t(本题恒为 3),时间复杂度为O(rows * cols * t),空间复杂度O(t)。对日常挑战的网格规模(用例中最大 6×6)而言完全不是问题。

常见错误与陷阱

结合判定条件(dr === dist || dc === dist) && (dr === 0 || dc === 0 || dr === dc),实现时最容易踩的坑有:

  1. 误用欧氏/曼哈顿距离:用Math.sqrt(dr*dr + dc*dc) === distdr + dc === dist都无法区分“直线上的点”与“L 形偏移的点”。例如对 45° 方向、dist = 2的塔,(dr=2, dc=2)的欧氏距离是2√2 ≠ 2,会漏掉对角线解。
  2. 忘记dr === 0 || dc === 0分支:如果只写dr === dc && dr === dist,就只能找出对角线方向的候选,水平/垂直方向的合法解全部被丢弃。
  3. 忘记跳过非零格:若不continue,信号塔自身所在格子(dr = 0, dc = 0)不会恰好满足任何正dist的约束,虽然不会误判,但属于无谓的无效检查;更重要的是在“手机恰好与某塔同格”这类变体中逻辑会崩坏。
  4. 返回值顺序:断言使用assert.deepEqual比较[row, col],返回[c, r]会直接失败。
  5. everysome混淆:题目要求“距所有塔都是正确格数”,必须全部满足;用some会导致任一单塔满足即返回,产生假解。

题目在 freeCodeCamp 课程体系中的位置

理解这道题的运行环境,有助于知道它如何被交付给学习者:

挑战类型定义。文件头元数据中challengeType: 28对应共享包 challenge-types.ts 中的dailyChallengeJs = 28(Python 版每日挑战为dailyChallengePy = 29)。同一文件定义了三类映射关系:

  • viewTypes[dailyChallengeJs]: 'classic'(见 challenge-types.ts)——每日挑战复用经典(classic)挑战视图渲染编辑器与测试面板;
  • submitTypes[dailyChallengeJs]: 'tests'(见 challenge-types.ts)——提交时运行的是“跑测试”型判定,即本文的 5 组assert.deepEqual断言;
  • getIsDailyCodingChallengegetDailyCodingChallengeLanguage两个工具函数把 28/29 归入每日挑战集合,并分别映射到'javascript'/'python'语言标签。

区块配置。daily-coding-challenges-javascript.json 声明了该区块的关键属性:helpCategory: "JavaScript"usesMultifileEditor: truedisableLoopProtectTests: trueblockLayout: "legacy-challenge-list",并且isUpcomingChange: true表明该区块处于待上线/变更状态。本区块从 “Challenge 1: Vowel Balance” 一直排到 “Challenge 365: The Last Challenge: Bucket Fill 3”,全年 365 天每日一题;本块同时被挂载在dev-playground超级区块下(见 dev-playground.json),供开发者/学习者在练习场中按序刷题。

运行时数据流。在线页面上,每日挑战由客户端组件 show-daily-coding-challenge.tsx 承接:组件在 第 157~163 行 按日期向${apiLocation}/daily-coding-challenge/day/{monthDay}发起请求,先经validateDailyCodingChallengeSchema做 JSON Schema 校验,再由formatChallengeData把数据库返回的 JavaScript 变体拼装成challengeType: 28helpCategory: 'JavaScript'的挑战节点(见 第 91~114 行),最终注入ShowClassic组件渲染——即学习者看到的正是本挑战文件的题面、测试断言与script.js编辑区。Python 变体(challengeType: 29,编辑区main.py)走同一管线,仅语言与文件键不同。

值得注意的边界:仓库中该挑战文件本身没有tests段落,5 组断言写在# --hints--小节内;而区块 JSON 与在线 API 管线(javascript.tests字段)共同承担了测试的最终交付,这也是该区块disableLoopProtectTests: true与在线校验流程配合的原因。

小结与自测建议

本题的通用解法可以归纳为三步:预提取全部约束源(信号塔)→ 为每个约束源定义单点判定函数(轴对齐/对角线距离)→ 全网格扫描并用every做约束交集,命中即返回。这一模式可迁移到本区块其他同类题目(如 Challenge 243: Rook Attack、Challenge 245: Spiral Matrix 等网格题,均位于 区块目录)。

自测步骤:

  1. 将本文给出的 5 组assert.deepEqual断言与你的findSignal实现放入同一个 Node.js 文件;
  2. 用 Node 内置的node:assert替换assert(或在 freeCodeCamp 编辑器内直接提交),确认全部通过;
  3. 对照“常见错误”一节,逐一验证水平/垂直/对角线三种方向在 5 组用例中的覆盖:用例 1、3 覆盖垂直与水平,用例 2、4、5 覆盖对角线——全部通过说明方向判定逻辑完备。

【免费下载链接】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/10 11:45:21

Python学生成绩分析系统开发实战

1. 项目概述&#xff1a;学生成绩分析系统的核心价值这个基于Python技术栈的成绩分析系统&#xff0c;本质上是一个将教育数据转化为决策依据的利器。我在实际教学管理工作中发现&#xff0c;传统Excel表格处理成绩数据存在三个致命缺陷&#xff1a;一是无法快速生成多维度统计…

作者头像 李华
网站建设 2026/9/10 11:42:01

LSTM-GRU混合模型在电力负荷预测中的实战应用

1. 电力负荷预测与LSTM-GRU混合模型实战电力系统调度中&#xff0c;负荷预测的准确性直接影响发电计划制定和电网安全运行。传统时间序列预测方法&#xff08;如ARIMA&#xff09;在处理非线性、多因素的电力负荷数据时往往表现不佳。我在某省级电网公司的实际项目中&#xff0…

作者头像 李华
网站建设 2026/9/10 11:39:20

Profile Lookup:6 条断言到稳健实现

Profile Lookup&#xff1a;6 条断言到稳健实现 【免费下载链接】TVBoxOSC TVBoxOSC - 一个基于第三方项目的代码库&#xff0c;用于电视盒子的控制和管理。 项目地址: https://gitcode.com/GitHub_Trending/tv/TVBoxOSC Profile Lookup 这道题看着只是"查通讯录&q…

作者头像 李华