)
freeCodeCamp 每日编程挑战第 20 题用 Python 双集合法求解数组重复元素Array Duplicates【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp本篇技术指南围绕 freeCodeCamp 开源仓库中的 Python 每日编程挑战 Challenge 20: Array Duplicates挑战文件位于 curriculum/challenges/english/blocks/daily-coding-challenges-python/6821ebee237de8297eaee796.md展开。文章完整继承原挑战的题目描述、三组测试断言、种子代码与官方参考解法并结合仓库中该功能块的结构配置、前端挑战渲染、API 数据链路与种子脚本源码深入讲解题目背后的算法思路、challengeType: 29的运行机制以及如何从源码层面理解这一每日一题的完整生命周期。读完本文你将掌握找出数组中出现多次的元素并按升序去重返回的经典 Python 实现并能独立分析、实现同类题型。一、题目概览找出数组中的重复元素原挑战文档以 YAML frontmatter 定义了挑战元信息正文包含四个关键部分--description--题目描述、--hints--测试断言、--seed--起始代码与--solutions--官方参考解。题目原文如下Given an array of integers, return an array of integers that appear more than once in the initial array, sorted in ascending order. If no values appear more than once, return an empty array.Only include one instance of each value in the returned array.翻译并拆解需求可以得到三条必须同时满足的判定规则重复判定某个整数在输入数组中出现的次数大于 1才算重复元素去重输出返回数组中每个重复值只保留一份实例不输出次数、不输出频次信息升序排序最终返回的数组必须按数值升序排列若输入中没有重复元素则返回空数组[]。以题目给出的第三个测试用例为例输入[2, 34, 0, 1, -6, 23, 5, 3, 2, 5, 67, -6, 23, 2, 43, 2, 12, 0, 2, 4, 4]中2出现了 6 次、0、-6、23、5、4各出现 2 次因此输出为[-6, 0, 2, 4, 5, 23]。注意2无论出现多少次都只输出一次67、34等只出现一次的元素则被排除。该挑战在块内的完整定义含 id、dashedName、challengeType可在 curriculum/structure/blocks/daily-coding-challenges-python.json 中确认其challengeOrder数组将本挑战id 为6821ebee237de8297eaee796列为第 20 题。二、题目在 freeCodeCamp 中的定位每日编程挑战Daily Coding Challenge体系这道题并不是孤立的一道练习题而是 freeCodeCamp 每日编程挑战Daily Coding Challenge功能块中的一员。从 curriculum/structure/blocks/daily-coding-challenges-python.json 可以看到该块的配置{ isUpcomingChange: true, dashedName: daily-coding-challenges-python, usesMultifileEditor: true, helpCategory: Python, blockLayout: legacy-challenge-list }其中关键字段的含义isUpcomingChange: true表示该功能块属于即将上线的新功能默认不会在正式站点对外展示usesMultifileEditor: true挑战使用多文件编辑器渲染helpCategory: Python本题归类为 Python 帮助类别blockLayout: legacy-challenge-list使用经典挑战列表布局。该块的challengeOrder从 Challenge 1: Vowel Balance 一直排到 Challenge 365而本挑战恰好是第 20 题。Python 版块与 JavaScript 版块curriculum/structure/blocks/daily-coding-challenges-javascript.json一一对应、逐日交替二者共用同一份题目描述仅在测试断言与语言写法上不同——在 tools/daily-challenges/helpers.ts 的combineChallenges函数中会强制校验 JavaScript 与 Python 两个版本的title与description完全一致否则抛错。从客户端渲染源码 client/src/client-only-routes/show-daily-coding-challenge.tsx 可以看出同一道挑战在运行时会被拆分为 JavaScriptchallengeType: 28与 PythonchallengeType: 29两套挑战数据本挑战对应的challengeType: 29正是 Python 版每日挑战的专属类型标识。前端通过fetch(${apiLocation}/daily-coding-challenge/day/${monthDay})按 MM-DD 日期拉取当天挑战并经validateDailyCodingChallengeSchema见 client/src/utils/daily-coding-challenge-validator.ts校验数据完整性后将challengeType: 29的 Python 数据渲染为main.py文件供学习者作答。三、测试断言Hints逐条解析原挑战文档在--hints--区块中给出了三条由runPython包装的单元测试测试框架使用 Python 标准库unittest的TestCase().assertEqual进行断言。逐条解析如下。3.1 无重复元素场景({test: () { runPython( from unittest import TestCase TestCase().assertEqual(find_duplicates([1, 2, 3, 4, 5]), [])) }})输入[1, 2, 3, 4, 5]五个整数各出现一次没有任何重复因此期望返回空列表[]。这条用例验证的是无重复时的边界行为。3.2 存在多个重复元素的场景({test: () { runPython( from unittest import TestCase TestCase().assertEqual(find_duplicates([1, 2, 3, 4, 1, 2]), [1, 2])) }})输入[1, 2, 3, 4, 1, 2]其中1与2各出现两次3、4出现一次。期望输出[1, 2]——注意1、2在输入中本就按升序排列但题目要求与实现都必须显式保证升序不能依赖输入顺序的巧合。3.3 乱序、负数与高频重复的复合场景({test: () { runPython( from unittest import TestCase TestCase().assertEqual(find_duplicates([2, 34, 0, 1, -6, 23, 5, 3, 2, 5, 67, -6, 23, 2, 43, 2, 12, 0, 2, 4, 4]), [-6, 0, 2, 4, 5, 23])) }})输入数组包含 21 个整数混有负数-6、零、大数67、43以及高频重复2出现 6 次。期望输出[-6, 0, 2, 4, 5, 23]。这条用例同时验证了三件事负数参与排序-6排在最前、只出现一次的元素如34、1、3、67、43、12被排除、同一值无论重复多少次都只输出一次。这三组用例共同构成了对重复判定 去重 升序排序 空数组边界的完整覆盖。仓库中 JavaScript 同款挑战curriculum/challenges/english/blocks/daily-coding-challenges-javascript/6821ebee237de8297eaee796.md使用assert.deepEqual对findDuplicates做了完全等价的断言二者测试数量必须一致combineChallenges中对此有显式校验这保证了每日挑战的 JS / Python 双版本难度与判定完全对齐。四、起始代码Seed与作答要求原挑战给出的种子代码如下def find_duplicates(arr): return arr学习者需要在保持函数签名find_duplicates(arr)不变的前提下填充实现并保证函数接收一个整数列表arr返回一个新的列表返回结果不得修改原数组原文档虽未显式禁止原地修改但返回新数组是更安全的惯例输出满足三条判定规则重复、去重、升序。这套挑战运行于 freeCodeCamp 的多文件 Python 环境challengeType: 29对应main.py文件文件键为mainpy见 client/src/client-only-routes/show-daily-coding-challenge.tsx 中fileKey: mainpy、ext: py、name: main的配置。五、官方参考解双集合Two-Set法原挑战在--solutions--中给出的参考实现是经典的双集合扫描法def find_duplicates(arr): seen set() duplicates set() for num in arr: if num in seen: duplicates.add(num) else: seen.add(num) return sorted(duplicates)5.1 算法思路逐步拆解维护两个集合seen记录已经出现过至少一次的元素duplicates记录已经确认重复的元素单次线性扫描遍历arr中的每个元素num若num已经在seen中说明这是第二次或更多次出现属于重复元素将其加入duplicates否则是首次出现将其加入seen返回前排序duplicates是集合无序用sorted()转换为按数值升序排列的列表作为最终返回值。以用例三为例模拟执行遍历元素seen处理后duplicates处理后2{2}{}34{2, 34}{}0{2, 34, 0}{}1{2, 34, 0, 1}{}-6{2, 34, 0, 1, -6}{}23{…, 23}{}5{…, 5}{}3{…, 3}{}2第二次不变{2}5第二次不变{2, 5}…不变持续追加4第二次不变{-6, 0, 2, 4, 5, 23}最终sorted(duplicates)得到[-6, 0, 2, 4, 5, 23]与测试断言完全一致。5.2 复杂度分析时间复杂度O(n)用于线性扫描集合的in判断与add均为平均O(1)加上O(k log k)用于对k个重复元素排序k ≤ n整体为O(n log n)最坏情况下全部元素都重复时退化为O(n log n)空间复杂度O(n)最坏情况下seen与duplicates合计需要存储至多n个不同元素。该解法同时天然满足只保留一份实例的要求——duplicates是集合add操作对同一值重复调用不会产生重复项无需额外去重逻辑。六、其他可行解法与对比除官方双集合法外本题还有多种等价实现可作为面试或教学中的延伸讨论。6.1 基于collections.Counter的频次统计法from collections import Counter def find_duplicates(arr): return sorted(num for num, count in Counter(arr).items() if count 1)Counter一次遍历完成频次统计再用生成器筛选频次大于 1 的键并排序。代码最简洁但需要额外引入collections模块且内部同样要维护完整频次字典空间开销与双集合法同级。6.2 排序后相邻比较法def find_duplicates(arr): arr sorted(arr) result [] for i in range(1, len(arr)): if arr[i] arr[i - 1] and (not result or result[-1] ! arr[i]): result.append(arr[i]) return result先整体排序再扫描相邻元素是否相等利用结果列表最后一个元素来避免连续重复值被多次记录。该方法空间开销小原地排序时O(1)额外空间但排序本身是O(n log n)且逻辑分支比集合法略复杂。6.3 暴力双重循环不推荐def find_duplicates(arr): result [] for i in range(len(arr)): if arr[i] in result: continue for j in range(i 1, len(arr)): if arr[i] arr[j]: result.append(arr[i]) break return sorted(result)时间复杂度为O(n²)仅适合教学演示为什么需要更高效解法不应作为实际提交方案。三种方案对比小结方案时间复杂度空间复杂度代码简洁度双集合法官方解O(n log n)O(n)高Counter 频次法O(n log n)O(n)最高排序相邻比较法O(n log n)O(1)原地排序中暴力双重循环O(n²)O(n)低七、从源码理解这道题如何进入每日挑战并完成评测在 freeCodeCamp 仓库中本挑战的完整生命周期由以下环节构成理解这条链路有助于你把握challengeType: 29与runPython背后的真实运行机制。7.1 种子写入从课程文件到数据库tools/daily-challenges/seed-daily-challenges.ts 是每日挑战的种子脚本它通过 GraphQL 端点http://localhost:8000/___graphql客户端需以显示即将上线内容模式运行从dev-playground超级块中按block: {eq: daily-coding-challenges-python}过滤拉取全部 Python 挑战对应脚本fetchChallenges(python)JavaScript 同理脚本强制校验 JS 与 Python 挑战数量一致且总数必须等于EXPECTED_CHALLENGE_COUNT 365否则直接抛错以2025-08-11为起始日期按天递增为每个挑战分配date调用combineChallengestools/daily-challenges/helpers.ts将同一题号的 JS/Python 双版本合并为一条记录含tests与challengeFiles_id直接复用挑战 id并写入 MongoDB 的DailyCodingChallenges集合起始日期通过硬编码断言2025-08-11T00:00:00.000Z保护防止发布后被无意修改。7.2 API 读取按日期暴露挑战数据api/src/daily-coding-challenge/routes/daily-coding-challenge.ts 提供六条公开 GET 路由路由参数格式说明/daily-coding-challenge/date/:dateYYYY-MM-DD按完整日期取当天挑战未来日期返回 404/daily-coding-challenge/day/:dayMM-DD按月-日取挑战前端主用/daily-coding-challenge/today无取美国中部时间今天的挑战/daily-coding-challenge/month/:monthYYYY-MM取某月全部挑战仅返回 id/编号/日期/标题/daily-coding-challenge/all无取截至今日的全部挑战元信息/daily-coding-challenge/newest无返回最新一条挑战的日期其中日期解析与源日期映射逻辑在 api/src/daily-coding-challenge/utils/helpers.tsdateStringToUtcMidnight严格校验YYYY-MM-DD格式并转为 UTC 零点非法格式返回null路由随即返回 400monthDayStringToUtcDate以 2000 年闰年为占位年解析MM-DD利用Date.UTC的滚动特性检测 2 月 30 日等非法日期getSourceDate将任意日期映射回 2025-08-11 至 2026-08-10 这一年的原始挑战周期——8 月 11 日及之后取 2025 年之前取 2026 年且 2 月 29 日统一映射到 2 月 28 日实现每天一道、循环复用的效果所有查询都限定date today(US Central)保证未来的挑战不会被提前泄露。响应体结构由 api/src/daily-coding-challenge/schemas/daily-coding-challenge.ts 中的 TypeBox schema 严格约束单条挑战响应包含id、date、challengeNumber、title、description以及javascript与python两个语言对象每个语言对象又含teststexttestString与challengeFilescontentsfileKey。本挑战的testString正是第三节中那些包裹着unittest断言的runPython代码。7.3 前端渲染与评测client/src/client-only-routes/show-daily-coding-challenge.tsx 完成最后的组装拉取数据后补上section iddescription包裹的描述、构造challengeType: 29的 Python 挑战节点与main.py文件最终交给ShowClassic组件渲染。评测时平台将学习者提交的find_duplicates函数与testString中的runPython测试代码合并执行——from unittest import TestCase意味着每次提交都会实例化一个真实unittest.TestCase并通过assertEqual严格比对输出列表。此外api/src/daily-coding-challenge/routes/daily-coding-challenge.test.ts 通过vi.useFakeTimers固定当前时间对上述路由的 400非法格式、404未找到 / 未来日期、200正常返回等场景做了完整覆盖例如非法日期列表[invalid-format, 2025-07, 07-18-2025, ...]全部应返回 400这从侧面印证了YYYY-MM-DD参数的严格性。八、小结与延伸练习Array Duplicates 是数组 集合 排序三类基础数据结构的综合应用题它要求开发者正确区分出现次数统计与去重集合两种语义并在一次线性扫描内完成状态维护。官方双集合法在正确性、可读性与性能之间取得了良好平衡可作为同类题型如找出唯一未重复元素返回出现次数最多的元素的通用模板。如果你想继续深入研究建议阅读以下仓库文件curriculum/challenges/english/blocks/daily-coding-challenges-python/6821ebee237de8297eaee796.md本题原始定义描述、断言、种子、官方解curriculum/challenges/english/blocks/daily-coding-challenges-javascript/6821ebee237de8297eaee796.md同题的 JavaScript 版本findDuplicatescurriculum/structure/blocks/daily-coding-challenges-python.jsonPython 挑战块的完整 365 题编排tools/daily-challenges/seed-daily-challenges.ts每日挑战入库脚本api/src/daily-coding-challenge/routes/daily-coding-challenge.ts挑战数据 APIclient/src/client-only-routes/show-daily-coding-challenge.tsx前端渲染与语言切换逻辑。延伸练习建议在不使用集合的情况下实现本题要求空间复杂度 O(1) 且允许修改输入数组或将本题推广为返回出现次数超过n/3的元素体会摩尔投票等进阶算法的适用边界。【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考