首页
/ freeCodeCamp 每日编程挑战实战解析:Challenge 290 "Pizza Party" 的向上取整与聚合算法

freeCodeCamp 每日编程挑战实战解析:Challenge 290 "Pizza Party" 的向上取整与聚合算法

2026-09-09 19:44:12作者:虞亚竹Luna

本篇技术指南以 freeCodeCamp 仓库中的 Challenge 290: Pizza Party(每日编程挑战系列之一)为核心,从题目规则、数学建模、逐行代码实现到测试用例验证,完整剖析这道数组聚合类算法题。读者读完将掌握 Math.ceil 向上取整、Math.max 下限约束、Array.reduce 聚合等 JavaScript 核心技巧,并了解该挑战在 freeCodeCamp 每日编程挑战体系(challengeType 28)中的真实运行机制。

一、题目总览:为披萨派对计算订购数量

挑战文件位于 curriculum/challenges/english/blocks/daily-coding-challenges-javascript/69f8c998d78ad3171a0713bd.md,归属于 daily-coding-challenges-javascript 挑战块。题目场景非常贴近生活:

给定一个数组,表示今天每个人的工作时长(小时),请返回为披萨派对需要订购的披萨数量。

要完成这道题,必须严格遵循以下四条业务规则:

  1. 切片换算:将每个人的工作时长除以 3,得到其应得的披萨切片数;
  2. 禁止半片:不能吃半片披萨,因此每个人的切片数需向上取整到最近的整数;
  3. 最低保障:每个人至少分到 2 片;
  4. 整披萨订购:每份披萨含 8 片,总切片数换算成披萨后需向上取整到最近的整数个披萨。

这是一个典型的"分步计算 → 逐项聚合 → 最终取整"的数组处理问题,考察的是对 reduceMath.ceilMath.max 的组合运用能力。

二、从题目到数学模型

在动手写代码之前,先把文字规则翻译成数学表达式。

设输入数组为 hoursWorked(每人工作时长的数组),那么:

第 1 步:单人的切片数

slices_i = max( ceil(hours_i / 3), 2 )
  • ceil(hours_i / 3):实现"除以 3 后向上取整",对应规则 1 和规则 2;
  • max(..., 2):实现"每人至少 2 片"的下限约束,对应规则 3。

第 2 步:总切片数

totalSlices = sum(slices_i)  // 对每个人累加

第 3 步:最终披萨数

pizzas = ceil(totalSlices / 8)
  • 每片披萨 8 片、向上取整,对应规则 4。

值得注意的是,Math.max(Math.ceil(hours / 3), 2) 这一表达式把"取整"和"下限"两个逻辑合并成了一步:先算出按工时换算的切片数并取整,再与 2 比较取较大值。这样即使某人的工时小于 6 小时(ceil(hours/3) < 2),也能保证其至少获得 2 片。

三、种子代码与解决方案逐行拆解

3.1 初始种子代码

挑战为学习者提供了如下函数骨架(位于原文档的 # --seed-- 部分):

function getPizzasToOrder(hoursWorked) {

  return hoursWorked;
}

学习者需要把 return hoursWorked 替换为真正的计算逻辑,使函数接收每人工作时长数组、返回订购披萨数。

3.2 官方解决方案

function getPizzasToOrder(hoursWorked) {
  const totalSlices = hoursWorked.reduce((sum, hours) => {
    return sum + Math.max(Math.ceil(hours / 3), 2);
  }, 0);

  return Math.ceil(totalSlices / 8);
}

逐行解读:

代码片段 作用
hoursWorked.reduce((sum, hours) => ..., 0) 遍历数组,以 0 为初始值累加每个人的切片数
Math.ceil(hours / 3) 每小时 3 片换算并向上取整(规则 1、2)
Math.max(..., 2) 与 2 比较,保证每人最少 2 片(规则 3)
Math.ceil(totalSlices / 8) 每披萨 8 片,总切片换算后向上取整(规则 4)

整个方案只用一次 reduce 就完成了所有人的切片数聚合,再通过一次 Math.ceil 收尾,时间复杂度和空间复杂度均为 O(n)(仅需遍历一次数组、无额外存储),是这道题在性能上的最优解。

四、测试用例验证:理解边界与陷阱

原文档的 # --hints-- 部分给出了 5 个断言用例,每个都用 assert.equal 校验函数输出。下面逐一演算,帮助理解规则之间的交互:

用例 1:getPizzasToOrder([8, 8, 8])2

每人 8 小时:ceil(8/3) = 3,均大于 2,故每人 3 片,共 9 片;ceil(9/8) = 2。✅

用例 2:getPizzasToOrder([10, 9, 8, 2, 2, 6, 10])3

  • 10 小时 → ceil(10/3) = 4
  • 9 小时 → ceil(9/3) = 3
  • 8 小时 → ceil(8/3) = 3
  • 2 小时 → ceil(2/3) = 1,但 max(1, 2) = 2
  • 2 小时 → 同上,2 片
  • 6 小时 → ceil(6/3) = 2
  • 10 小时 → 4 片

合计 4+3+3+2+2+2+4 = 20 片;ceil(20/8) = 3。✅ 这个用例同时覆盖了"最低 2 片保障"和"整披萨取整"两条规则。

用例 3:getPizzasToOrder([1, 2, 3, 4, 5])2

  • 1 小时 → max(ceil(1/3), 2) = 2
  • 2 小时 → max(1, 2) = 2
  • 3 小时 → max(1, 2) = 2
  • 4 小时 → max(ceil(4/3), 2) = max(2, 2) = 2
  • 5 小时 → max(2, 2) = 2

合计 10 片;ceil(10/8) = 2。✅ 该用例集中考验"工时不足 3 小时也要 2 片"的边界逻辑。

用例 4:getPizzasToOrder([8, 8, 8, 8, 8, 8, 8, 8])3

8 人各 3 片,共 24 片;ceil(24/8) = 3。✅ 恰好整批 3 个披萨,验证无余数时的取整行为。

用例 5:getPizzasToOrder([9, 9, 6])1

  • 9 小时 → ceil(9/3) = 3(两次)
  • 6 小时 → ceil(6/3) = 2

合计 8 片;ceil(8/8) = 1。✅ 恰好 1 个披萨,再次验证"整除"场景。

用例 6:getPizzasToOrder([10, 12, 16, 9, 8, 11, 15, 8, 0])5

  • 10 → 4、12 → 4、16 → ceil(16/3)=6、9 → 3、8 → 3、11 → ceil(11/3)=4、15 → 5、8 → 3、0 → max(0, 2) = 2

合计 4+4+6+3+3+4+5+3+2 = 34 片;ceil(34/8) = 5。✅ 该用例包含 0 工时(考验最低切片保障)和较大数值的聚合。

从这 6 组用例可以看出测试设计覆盖了:一般换算、下限约束、整除与不整除、0 工时等典型边界情况。

五、挑战在 freeCodeCamp 体系中的定位

5.1 挑战块与元数据

本挑战的 frontmatter 声明了 challengeType: 28dashedName: challenge-290,并在 curriculum/structure/blocks/daily-coding-challenges-javascript.json 中注册为 "Challenge 290: Pizza Party"(id 为 69f8c998d78ad3171a0713bd),前后分别是 Challenge 289 "Sum of Differences" 与 Challenge 291 "FizzBuzz Count"。

从该块配置可看出其工程特征:

  • "isUpcomingChange": true:属于进行中的课程变更;
  • "usesMultifileEditor": true:在挑战编辑界面使用多文件编辑器;
  • "helpCategory": "JavaScript":归入 JavaScript 帮助分类;
  • "blockLayout": "legacy-challenge-list":采用传统挑战列表布局。

5.2 每日挑战的 API 支撑

此类挑战的数据由 API 侧的 daily-coding-challenge 模块对外提供。路由实现位于 api/src/daily-coding-challenge/routes/daily-coding-challenge.ts,共注册了 6 个公开 GET 端点:

端点 说明
/daily-coding-challenge/date/:date YYYY-MM-DD 精确日期获取某天挑战
/daily-coding-challenge/day/:day MM-DD 获取对应日期的挑战(2 月 29 日映射到 2 月 28 日)
/daily-coding-challenge/today 获取美国中部时区(America/Chicago)当天的挑战
/daily-coding-challenge/month/:month YYYY-MM 获取整月挑战列表
/daily-coding-challenge/all 获取全部挑战列表(仅返回已发布日期)
/daily-coding-challenge/newest 获取最新一期挑战的日期

请求/响应结构由 TypeBox schema 定义在 api/src/daily-coding-challenge/schemas/daily-coding-challenge.ts:单个挑战响应包含 iddatechallengeNumbertitledescription 以及 javascript / python 两套语言字段(各含 testschallengeFiles)。Pizza Party 这类题目的测试断言正是在此类结构中以 testString 形式交付给前端执行的。

5.3 日期相关的实现细节

源码中有几个值得注意的日期处理细节(见 api/src/daily-coding-challenge/utils/helpers.ts 与路由文件):

  • "今天"以美国中部时间为准,由 getNowUsCentral() 计算,再经 getUtcMidnight 归一化为 UTC 零点用于数据库查询;
  • getSourceDate 会把 MM-DD 映射回具体的 UTC 日期,并处理闰年 2 月 29 日这类边界;
  • 所有查询都会过滤 date <= today US Central 的挑战,避免返回未发布的未来题目(路由注释明确指出最后一个挑战日期为 2026 年 8 月 10 日之后将不再返回数据)。

六、举一反三:从本题延伸的实战要点

6.1 取整三件套的选择

  • Math.ceil(x):向上取整,适合"必须准备足量"的场景(本题的披萨、物资采购、服务器容量规划);
  • Math.floor(x):向下取整,适合"最多能装多少"的场景;
  • Math.round(x):四舍五入,适合统计平均类场景。

6.2 用 Math.max 施加下限约束

Math.max(value, min) 是给计算结果设置下限的惯用写法,等价于 value < min ? min : value。同理,Math.min(value, max) 可以施加上限。本题的"每人至少 2 片"正是这种模式的典型应用。

6.3 reduce 的聚合思维

当需要把数组中的每一项折算成一个数值时,reduce(callback, initialValue) 是最直接的工具。本题把"每人切片数"在回调内即时计算并累加,避免先 mapsum 的两趟遍历,代码更紧凑。若希望可读性优先,也可拆成两步:

function getPizzasToOrder(hoursWorked) {
  const slicesPerPerson = hoursWorked.map(hours =>
    Math.max(Math.ceil(hours / 3), 2)
  );
  const totalSlices = slicesPerPerson.reduce((sum, s) => sum + s, 0);
  return Math.ceil(totalSlices / 8);
}

两种写法结果一致,前者更高效,后者更利于调试中间值。

6.4 可能的陷阱

  • 忘记最低切片保障:若直接 sum += Math.ceil(hours / 3),则 [1, 2] 这类用例会得到 ceil(1/3)+ceil(2/3) = 2 片、1 个披萨,而正确答案要求每人 2 片,即 4 片、1 个披萨——对于本例恰好一致,但在更大的输入下会显著低估订购量;
  • 先取整再求和 vs 先求和再取整:必须"每人分别向上取整后再累加",而不是"总工时除以 3 后一次性取整",否则会系统性少算切片;
  • 忽略 0 工时成员ceil(0/3) = 0,若不做 max(..., 2) 约束,0 工时者将得不到任何切片。

七、总结

Challenge 290: Pizza Party 是一道短小精悍的数组聚合题:通过"工时 ÷ 3 向上取整、每人至少 2 片、每 8 片 1 个披萨向上取整"三层规则,串起了 Math.ceilMath.maxArray.prototype.reduce 三个高频 API,并用 6 组覆盖边界条件的断言固化了正确行为。学习者既可以把它当作日常算法练习,也可以顺着本仓库的 challenge 文档挑战块配置API 路由实现 三条线索,完整理解一道每日编程挑战从题目定义、数据提供到在线判题的全链路工程实现。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
899
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
525