news 2026/9/10 4:14:23

freeCodeCamp 每日编码挑战解析:Connect 3 三连棋检测算法(Challenge 322)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
freeCodeCamp 每日编码挑战解析:Connect 3 三连棋检测算法(Challenge 322)

freeCodeCamp 每日编码挑战解析:Connect 3 三连棋检测算法(Challenge 322)

【免费下载链接】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 板块第 322 题 "Connect 3" 为对象,完整还原题目规则、输入输出格式、全部测试用例与官方参考解答,并结合仓库源码讲解其运行机制与算法原理。读完本文,你将掌握矩阵方向枚举、边界检查、坐标规范化等二维数组遍历的核心技巧,并了解该挑战在 freeCodeCamp 课程体系与后端 API 中的实际位置。

挑战背景:freeCodeCamp 的每日编码挑战体系

本挑战文档位于curriculum/challenges/english/blocks/daily-coding-challenges-javascript/6a19b1062d1b153d8ac76d70.md,归属于 JavaScript 每日编码挑战块(block)。从 块结构配置 可以看到,整个块共包含 365 道挑战,按天数编号(Challenge 1 至 Challenge 365),这正是"每日一题"的设计初衷——一年 365 天每天一道编码练习题。

从仓库结构看,该功能在项目中是完整闭环的:

  • 课程内容:挑战文档以 Markdown 形式存放在curriculum/challenges/english/blocks/daily-coding-challenges-javascript/目录下,每份文档包含题目描述(--description--)、测试用例(--hints--)、初始代码(--seed--)与参考解答(--solutions--)四部分;
  • 数据落地:种子脚本 seed-daily-challenges.ts 从 GraphQL 拉取挑战数据并写入 MongoDB 的DailyCodingChallenges集合,起始日期被固定为2025-08-11T00:00:00.000Z,此后每个挑战顺延一天,脚本还内置了数量校验(期望 365 道)与起始日期校验,防止发布后误改;
  • 后端服务:daily-coding-challenge.ts 提供了按日期、按天、按月份、全部以及最新等 6 个只读 GET 接口,供前端按日拉取题目信息;
  • 前端展示:客户端通过/learn/daily-coding-challenge/路由渲染当日挑战(见 show-daily-coding-challenge.tsx),并用 daily-coding-challenge-validator.ts 中的 Joi schema 校验后端返回的数据结构。

Connect 3(Challenge 322)正是这 365 道题中的一道二维数组遍历类题目,位于该块的后期部分。

问题陈述精读

输入

函数connectThree(matrix)接收一个二维数组(矩阵)作为输入,矩阵中的每个单元格是以下三种值之一:

取值含义
"R"红色玩家的棋子
"Y"黄色玩家的棋子
""空字符串,表示该格为空

判定规则

"三连"(three in a row)的定义是:水平、垂直或对角线上存在三个连续的、类型相同的非空单元格。也就是说,三个"R"或三个"Y"必须紧挨着排成一条直线。

返回值

函数需要返回两种结果之一:

  1. 有赢家时:返回一个扁平数组,格式为["R", [0,2], [1,3], [2,4]],即依次是赢家标识("R""Y")和三个获胜格子的坐标。坐标以[行, 列]形式给出,且必须按"从上到下、从左到右"的顺序排列(top-to-bottom, then left-to-right);
  2. 无赢家时:返回空数组[]

测试用例逐条剖析

原文档共给出 5 组测试用例,覆盖了水平、垂直、正对角线、反对角线和无赢家五种典型场景,是理解题意的关键素材。

用例 1:水平三连

connectThree([["", "", "", ""], ["", "", "", ""], ["", "Y", "", ""], ["Y", "R", "R", "R"]]) // 应返回 ["R", [3, 1], [3, 2], [3, 3]]

棋盘第 4 行(索引 3)从左到右是["Y", "R", "R", "R"],其中R在列索引 1、2、3 上连续出现三个,构成水平三连。赢家为"R",坐标按从左到右返回。

用例 2:垂直三连

connectThree([["", "", "", ""], ["", "Y", "Y", ""], ["", "Y", "R", "R"], ["", "Y", "R", "R"]]) // 应返回 ["Y", [1, 1], [2, 1], [3, 1]]

观察列索引 1:行 1、2、3 的取值分别是"Y""Y""Y",构成垂直三连。赢家为"Y",坐标按从上到下返回。

用例 3:正对角线三连

connectThree([["", "", "Y", "R"], ["", "Y", "R", "Y"], ["", "R", "Y", "R"], ["", "R", "Y", "R"]]) // 应返回 ["R", [0, 3], [1, 2], [2, 1]]

(0,3)(1,2)(2,1)三个格子的值依次是"R""R""R",沿"右上到左下"的对角线方向(即行递增、列递减)连成一线。

用例 4:反对角线三连

connectThree([["", "Y", "", ""], ["", "Y", "Y", ""], ["", "R", "R", "Y"], ["R", "R", "Y", "R"]]) // 应返回 ["Y", [0, 1], [1, 2], [2, 3]]

(0,1)(1,2)(2,3)三个格子均为"Y",沿"左上到右下"方向(行、列同时递增)连成一线。

用例 5:无赢家

connectThree([["Y", "R", "R", "Y"], ["R", "Y", "Y", "R"], ["Y", "R", "R", "Y"], ["R", "Y", "Y", "R"]]) // 应返回 []

该棋盘是一个交替排列的Y/R棋盘,任何方向上都找不到三个连续同类棋子,因此返回空数组。

初始代码与求解思路

题目给出的种子代码(--seed--部分)非常简单:

function connectThree(matrix) { return matrix; }

它原样返回矩阵,显然不满足任何测试用例,需要你补全算法。整体求解思路分三步:

第一步:枚举四个方向

三连只可能沿四个方向出现,因此只需检查四种"方向向量":

方向行增量 (dr)列增量 (dc)说明
水平向右01检查同一行
垂直向下10检查同一列
右下对角线11主对角线方向
左下对角线1-1副对角线方向

由于"水平向左"是"水平向右"的反方向,检查方向向量的正方向即可覆盖所有情况,无需重复枚举。

第二步:以每个非空格为起点延伸

遍历矩阵中的每一个格子,跳过空字符串。对每个非空格,沿四个方向依次检查第 1、2 个后续格(共 3 格),一旦遇到越界或类型不一致就中断该方向的检查。

第三步:命中后规范化坐标

如果某个方向连续收集到 3 个相同棋子,就对这 3 个坐标排序,使结果满足"从上到下、从左到右"的输出要求,再组装返回数组。

官方参考解答逐行解析

原文档--solutions--部分给出了完整的参考实现,逐段解析如下:

function connectThree(matrix) { const rows = matrix.length; const cols = matrix[0].length; const directions = [[0, 1], [1, 0], [1, 1], [1, -1]]; for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { const piece = matrix[r][c]; if (!piece) continue; for (const [dr, dc] of directions) { const cells = [[r, c]]; for (let i = 1; i < 3; i++) { const nr = r + dr * i; const nc = c + dc * i; if (nr < 0 || nr >= rows || nc < 0 || nc >= cols) break; if (matrix[nr][nc] !== piece) break; cells.push([nr, nc]); } if (cells.length === 3) { cells.sort(([r1, c1], [r2, c2]) => r1 !== r2 ? r1 - r2 : c1 - c2); return [piece, ...cells]; } } } } return []; }
  • 第 1-2 行:读取矩阵的行数rows与列数cols,用于后续边界判断;
  • 第 4 行:定义四个方向向量。这里选择向右、向下、右下、左下四个方向,恰好覆盖水平、垂直、主对角线、副对角线;
  • 第 6-7 行:双重循环遍历所有格子;
  • 第 8-9 行:取出当前格子的棋子,若为空字符串(!piece为真,注意""是假值)则跳过。这一行是性能关键:空单元格既不可能成为任何三连的起点,也无需作为终点检查,直接剪枝;
  • 第 11-21 行:对每个方向,从当前格开始沿方向延伸。cells数组初始只含起点;循环i从 1 到 2,检查第 2、3 个格子是否越界(第 14 行)或与起点棋子不同(第 15 行),任一条件不满足就break放弃该方向;
  • 第 23-26 行cells.length === 3意味着成功收集到三连。此时先对坐标排序:优先按行号升序(从上到下),行号相同时按列号升序(从左到右),最后用展开语法组装成[piece, ...cells]扁平数组并立即返回;
  • 第 30 行:全部遍历结束仍未找到三连,返回[]

注意排序回调中的解构写法:cells.sort(([r1, c1], [r2, c2]) => r1 !== r2 ? r1 - r2 : c1 - c2),它直接对坐标数组对[行, 列]解构,比较规则完全对应题目要求的输出顺序。

算法复杂度与正确性分析

  • 时间复杂度:最坏情况下需要遍历全部rows × cols个格子,每个格子最多检查 4 个方向、每个方向最多延伸 2 步,因此总复杂度为O(rows × cols × 4 × 2),即O(rows × cols)的线性级别。对于 4×4 这样的小棋盘,性能开销可以忽略;
  • 空间复杂度:除cells数组外没有额外的大型数据结构,为O(1)(不计入返回值);
  • 正确性论证:任意一条长度为 3 的直线段,其"最靠上(行号最小)、同行时最靠左(列号最小)"的端点一定会在遍历时先被访问,且该方向向量会被枚举到,因此所有潜在三连都会被检测,不会遗漏;反之,break条件保证了只有严格同类型且连续的格子才会被计入,不会产生误报。

实战验证:在本地运行测试

若想在本地验证你的实现,可以按如下步骤在 freeCodeCamp 仓库环境中操作:

  1. 确保已按 README.md 安装好 pnpm 依赖;
  2. 将上述参考解答放入 Node.js 环境(或直接在浏览器控制台)执行;
  3. 依次运行文档中的 5 组assert.deepEqual断言:
assert.deepEqual(connectThree([["", "", "", ""], ["", "", "", ""], ["", "Y", "", ""], ["Y", "R", "R", "R"]]), ["R", [3, 1], [3, 2], [3, 3]]); assert.deepEqual(connectThree([["", "", "", ""], ["", "Y", "Y", ""], ["", "Y", "R", "R"], ["", "Y", "R", "R"]]), ["Y", [1, 1], [2, 1], [3, 1]]); assert.deepEqual(connectThree([["", "", "Y", "R"], ["", "Y", "R", "Y"], ["", "R", "Y", "R"], ["", "R", "Y", "R"]]), ["R", [0, 3], [1, 2], [2, 1]]); assert.deepEqual(connectThree([["", "Y", "", ""], ["", "Y", "Y", ""], ["", "R", "R", "Y"], ["R", "R", "Y", "R"]]), ["Y", [0, 1], [1, 2], [2, 3]]); assert.deepEqual(connectThree([["Y", "R", "R", "Y"], ["R", "Y", "Y", "R"], ["Y", "R", "R", "Y"], ["R", "Y", "Y", "R"]]), []);

全部通过即代表实现正确。该挑战对应的块配置位于 daily-coding-challenges-javascript.json,其中challengeType: 28标记了这类题目的类型,而挑战文档本身存放在 challenge 文件 中。

拓展思考:从 Connect 3 到 Tic-Tac-Toe 与四子棋

Connect 3 的解法框架具有极强的可扩展性,可以推广到一系列同类博弈判定问题:

  • 推广连子数:将内层循环的i < 3改为i < n,即可检测任意长度的连子,例如五子棋的五连;
  • 推广到 Tic-Tac-Toe 完整判定:本块中的 Challenge 153: Tic-Tac-Toe 就是同族题目,需要同时处理"胜负判定"与"平局判定";
  • 方向向量表驱动:把directions数组抽象为可配置参数,能让同一套遍历逻辑复用于国际象棋、四子棋等不同棋类的攻击范围检查,例如本块中的 Challenge 243: Rook Attack 与 Challenge 217: Captured Chess Pieces 都涉及沿方向向量的棋盘扫描。

掌握"方向向量 + 边界剪枝 + 坐标规范化"这一组合拳,你就拥有了解决一大类棋盘/网格类算法题的通用的底层能力。

【免费下载链接】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 4:13:50

雷达术语到MATLAB代码:工程师的元语言激活指南

1. 这不是“抄书笔记”&#xff0c;而是雷达系统工程师的入门通关地图很多人看到《雷达系统分析与设计 MATLAB版 第3版》第1章标题——“定义和术语”&#xff0c;第一反应是&#xff1a;这有什么好读的&#xff1f;不就是背概念吗&#xff1f;翻两页就扔在书架上吃灰。我带过三…

作者头像 李华
网站建设 2026/9/10 4:13:13

STM32F103C8T6倒计时系统:共阴数码管+无源蜂鸣器实战

简介&#xff1a;本资源是一套基于STM32F103C8T6单片机的标准库开发项目&#xff0c;面向电子信息、物联网及自动化专业本科生与初阶工程师&#xff0c;聚焦嵌入式外设驱动核心能力训练——实现一位八段共阴数码管0–9倒计时显示与蜂鸣器定时报警联动。资源包含178个文件&#…

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

AI转行五大核心赛道实战指南:ML/DL/NLP/CV/RL深度拆解

1. 这不是“AI科普文”&#xff0c;而是一份帮你避开三年弯路的学科地图你点开这篇内容&#xff0c;大概率正站在一个真实的人生岔路口&#xff1a;想转行进AI领域&#xff0c;但刷到的全是“3个月速成大模型工程师”“Python入门到年薪50万”的标题&#xff1b;报过课&#xf…

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

hyperframes全景视频拼接:关键帧与光流传播实战解析

说起“hyperframes”&#xff0c;不同圈子的人搜到的东西可能完全不一样。搞计算机视觉的可能会想到光流法里对极几何约束下的关键帧增强&#xff0c;做三维重建的也许会联想到多视角立体匹配里的超级帧概念&#xff0c;但如果你是在视频制作、全景内容生产这些偏实操的领域搜这…

作者头像 李华