LeetCode 135. 分发糖果(Candy)贪心两次遍历解法详解:基于 doocs/leetcode 多语言源码剖析

发布时间:2026/10/17 1:49:42

LeetCode 135. 分发糖果(Candy)贪心两次遍历解法详解:基于 doocs/leetcode 多语言源码剖析 示例工程【免费下载链接】leetcodeLeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer第 2 版》、《程序员面试金典第 6 版》题解项目地址https://gitcode.com/doocs/leetcode点击查看免费下载本篇技术指南以 doocs/leetcode 仓库收录的 135. 分发糖果Candy题解 为核心系统讲解这道经典贪心题的问题建模、双向约束拆解思路、两次遍历算法的完整推导过程并逐一对照仓库中 Python3、Java、C、Go、TypeScript、Rust、C# 七种语言的源码实现。读完本文你将掌握左右约束互相独立、分别扫描、逐点取最大值这一贪心套路并能在面试中独立推导与正确性论证。题目解析问题重述与约束条件题目描述n个孩子站成一排给定整数数组ratings表示每个孩子的评分。需要按照以下两条要求给这些孩子分发糖果每个孩子至少分配到1个糖果相邻两个孩子中评分更高的那个会获得更多的糖果。目标是计算并返回需要准备的最少糖果数目。约束与数据规模1 n ratings.length 5 * 10^40 ratings[i] 5 * 10^4数据规模提示了算法边界n最大可达 5 万因此O(n log n)的排序算法可行但O(n^2)的暴力方案不可接受能够把问题压到O(n)的贪心/两次遍历解法才是本题期望的正解。评分可以取到0即评分允许为0此时该孩子依然至少需要1颗糖果。示例推导示例 1输入ratings [1,0,2] 输出5 解释分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。验证中间孩子评分0最低拿1颗左邻居评分1 0拿2颗、右邻居评分2 0拿2颗相邻约束全部满足总数2125为最优。示例 2输入ratings [1,2,2] 输出4 解释分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。 第三个孩子只得到 1 颗糖果这满足题面中的两个条件。这个示例是本题的陷阱所在第二、第三个孩子评分相等都是2约束只要求评分更高者糖果更多相等评分之间不要求比较因此第三个孩子完全可以拿1颗。若错误地把相等评分也视为需要区分会多算糖果。为什么这道题麻烦相等评分与双向约束排序思路为什么不可行一个直觉想法是按评分排序评分高的多分一些。但题目约束是局部的相邻比较而不是全局的排名关系。以ratings [3,1,2]为例若按评分1 2 3依次分配1、2、3颗对应到原数组得到[3,1,2]总数6而真正的最优分配是[2,1,2]评分3的孩子拿2颗即可满足比邻居1分孩子多总数只有5。可见排序后的全局偏序无法编码相邻约束排序方案给出的往往不是最小值。相等评分不触发更多糖果第二条规则只针对评分更高的相邻关系。两个相邻且评分相等的孩子之间没有大小关系可以各拿1颗。因此所有比较都必须使用严格大于而非大于等于这是实现正确性的关键细节也是示例 2 中第三个孩子拿1颗的依据。核心思路把两个方向的约束拆开处理原题解文档README.md 解法部分给出的方法一是两次遍历。其核心洞察是左、右两个方向的约束是互相独立的——评分比左边孩子高这条约束只需从左到右扫一遍即可满足评分比右边孩子高这条约束只需从右到左扫一遍即可满足每个孩子最终拿的糖果数取两侧要求的较大值两边的递增链都不会被破坏。定义 left 与 right 数组初始化两个数组left和right长度均为nleft[i]只考虑当前孩子比左边孩子评分高这一约束时当前孩子至少应获得的糖果数right[i]只考虑当前孩子比右边孩子评分高这一约束时当前孩子至少应获得的糖果数。初始时left[i] 1、right[i] 1因为每个孩子至少要有1颗糖果。第一次遍历从左到右填充 left从左到右遍历评分数组如果当前孩子比左边孩子评分高ratings[i] ratings[i-1]则left[i] left[i-1] 1否则保持left[i] 1。这条扫描保证了沿着评分严格递增的链条糖果数严格递增左向约束被完整编码进left。第二次遍历从右到左填充 right从右到左遍历评分数组如果当前孩子比右边孩子评分高ratings[i] ratings[i1]则right[i] right[i1] 1否则保持right[i] 1。对称地右向约束被完整编码进right。合并每人取两侧要求的最大值最后再遍历一遍每个孩子最终糖果数为candies[i] max(left[i], right[i])累加所有candies[i]即为答案。为什么取 max 不会破坏两侧约束这里有一个值得论证的细节单独看left和right各自都只满足一个方向的约束为什么逐点取最大值之后两个方向的约束依然同时成立以左向约束为例。设candies[i] max(left[i], right[i])若ratings[i] ratings[i-1]则从左到右扫描时left[i] left[i-1] 1。关键观察是此时在从右到左的扫描中right[i-1]不可能被right[i]更新——因为更新条件ratings[i-1] ratings[i]与已知条件ratings[i] ratings[i-1]矛盾所以right[i-1]恒为1。于是candies[i-1] max(left[i-1], 1) left[i-1] candies[i] left[i] left[i-1] 1 left[i-1] candies[i-1]左向约束成立。右向约束的论证完全对称若ratings[i] ratings[i1]则从右到左扫描时right[i] right[i1] 1而左向扫描中left[i1]不可能被更新ratings[i1] ratings[i]为假故left[i1] 1从而candies[i1] right[i1] right[i] candies[i]。这正是原文档思考部分所述每人取两侧要求的较大值两边的递增链都不会被破坏的严格化表述。算法步骤与手推示例算法步骤伪代码令n ratings.length初始化left [1]*nright [1]*n从左到右i从1到n-1若ratings[i] ratings[i-1]则left[i] left[i-1] 1从右到左i从n-2到0若ratings[i] ratings[i1]则right[i] right[i1] 1累加sum(max(left[i], right[i]))并返回。手推示例 2ratings [1,2,2]初始化left [1,1,1]right [1,1,1]左到右i1时2 1成立left[1] left[0]1 2i2时2 2不成立left[2]保持1。得到left [1,2,1]右到左i1时2 2不成立i0时1 2不成立。得到right [1,1,1]合并candies [max(1,1), max(2,1), max(1,1)] [1,2,1]总和4✓手推示例 1ratings [1,0,2]初始化left [1,1,1]right [1,1,1]左到右i1时0 1不成立i2时2 0成立left[2] left[1]1 2。得到left [1,1,2]右到左i1时0 2不成立i0时1 0成立right[0] right[1]1 2。得到right [2,1,1]合并candies [max(1,2), max(1,1), max(2,1)] [2,1,2]总和5✓两个示例的推演结果与原文档完全一致说明该算法正确覆盖了递增链与相等评分不区分两类情形。多语言实现仓库源码对照从源码结构看仓库为本题目录solution/0100-0199/0135.Candy收录了 7 种语言的同一套两次遍历实现分别位于Solution.py、Solution.java、Solution.cpp、Solution.go、Solution.ts、Solution.rs、Solution.cs与 README.md 中的代码标签页一一对应。Python3Solution.pyclass Solution: def candy(self, ratings: List[int]) - int: n len(ratings) left [1] * n right [1] * n for i in range(1, n): if ratings[i] ratings[i - 1]: left[i] left[i - 1] 1 for i in range(n - 2, -1, -1): if ratings[i] ratings[i 1]: right[i] right[i 1] 1 return sum(max(a, b) for a, b in zip(left, right))Python 实现借助zip(left, right)并行取最大值并求和代码最简洁可直接复制运行。JavaSolution.javaclass Solution { public int candy(int[] ratings) { int n ratings.length; int[] left new int[n]; int[] right new int[n]; Arrays.fill(left, 1); Arrays.fill(right, 1); for (int i 1; i n; i) { if (ratings[i] ratings[i - 1]) { left[i] left[i - 1] 1; } } for (int i n - 2; i 0; --i) { if (ratings[i] ratings[i 1]) { right[i] right[i 1] 1; } } int ans 0; for (int i 0; i n; i) { ans Math.max(left[i], right[i]); } return ans; } }Java 版本显式用Arrays.fill完成初始化注意右向遍历使用n - 2起始、i 0收尾的循环边界。CSolution.cppclass Solution { public: int candy(vectorint ratings) { int n ratings.size(); vectorint left(n, 1); vectorint right(n, 1); for (int i 1; i n; i) { if (ratings[i] ratings[i - 1]) { left[i] left[i - 1] 1; } } for (int i n - 2; ~i; --i) { if (ratings[i] ratings[i 1]) { right[i] right[i 1] 1; } } int ans 0; for (int i 0; i n; i) { ans max(left[i], right[i]); } return ans; } };C 版本通过vectorint left(n, 1)一步完成定长 初始化为 1右向循环条件写成~i等价于i 0利用补码特性使循环在i -1时自然终止。GoSolution.gofunc candy(ratings []int) int { n : len(ratings) left : make([]int, n) right : make([]int, n) for i : range left { left[i] 1 right[i] 1 } for i : 1; i n; i { if ratings[i] ratings[i-1] { left[i] left[i-1] 1 } } for i : n - 2; i 0; i-- { if ratings[i] ratings[i1] { right[i] right[i1] 1 } } ans : 0 for i, a : range left { b : right[i] ans max(a, b) } return ans }Go 版本先以for i : range left统一完成两个数组的初始化最后用内建泛型函数max(a, b)合并。TypeScriptSolution.tsfunction candy(ratings: number[]): number { const n ratings.length; const left new Array(n).fill(1); const right new Array(n).fill(1); for (let i 1; i n; i) { if (ratings[i] ratings[i - 1]) { left[i] left[i - 1] 1; } } for (let i n - 2; i 0; --i) { if (ratings[i] ratings[i 1]) { right[i] right[i 1] 1; } } let ans 0; for (let i 0; i n; i) { ans Math.max(left[i], right[i]); } return ans; }TypeScript 版本使用new Array(n).fill(1)批量初始化注意Array.fill对基本类型number是安全的不涉及引用共享问题。RustSolution.rsimpl Solution { pub fn candy(ratings: Veci32) - i32 { let n ratings.len(); let mut left vec![1; n]; let mut right vec![1; n]; for i in 1..n { if ratings[i] ratings[i - 1] { left[i] left[i - 1] 1; } } for i in (0..n - 1).rev() { if ratings[i] ratings[i 1] { right[i] right[i 1] 1; } } ratings .iter() .enumerate() .map(|(i, _)| left[i].max(right[i]) as i32) .sum() } }Rust 版本用vec![1; n]初始化右向遍历借助(0..n - 1).rev()生成递减区间最后以函数式风格map sum完成合并求和注意left[i].max(right[i])需要as i32转回函数返回类型。C#Solution.cspublic class Solution { public int Candy(int[] ratings) { int n ratings.Length; int[] left new int[n]; int[] right new int[n]; Array.Fill(left, 1); Array.Fill(right, 1); for (int i 1; i n; i) { if (ratings[i] ratings[i - 1]) { left[i] left[i - 1] 1; } } for (int i n - 2; i 0; --i) { if (ratings[i] ratings[i 1]) { right[i] right[i 1] 1; } } int ans 0; for (int i 0; i n; i) { ans Math.Max(left[i], right[i]); } return ans; } }C# 版本在方法、数组与类型命名上遵循 .NET 约定Candy、Length、Math.Max、Array.Fill核心逻辑与其余语言完全一致。复杂度分析时间复杂度$O(n)$。三次线性遍历左到右、右到左、合并求和各扫描一遍数组其中 $n$ 是评分数组长度。在n 5 * 10^4的约束下可以轻松通过。空间复杂度$O(n)$。需要两个长度为 $n$ 的辅助数组left和right也可合并为每个位置只保留一个最大值但本题两数组实现更直观可读性更好。总结与延伸本题是贪心思想中将相互耦合的约束拆解为多个独立方向分别扫描后合并的典型代表与「接雨水」等题目的双端扫描思路一脉相承。解题时要牢牢把握三个要点约束只作用于相邻且评分严格更高的关系相等评分不产生额外需求左、右两个方向的约束互相独立可以分别用一次线性扫描独立满足每个位置取两侧需求的最大值既不会破坏任何一侧的递增链又能保证全局最少。仓库为本题目录solution/0100-0199/0135.Candy提供了 7 种语言的对照实现英文版本可参考 README_EN.md。在面试中复现本题时建议先口头推导两个示例的left/right数组填充过程再落笔写出两次遍历代码并简要论证取最大值为何不破坏双向约束即可完整展示算法能力。赞分享示例工程【免费下载链接】leetcodeLeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer第 2 版》、《程序员面试金典第 6 版》题解项目地址https://gitcode.com/doocs/leetcode点击查看免费下载相关推荐CsvHelper v24 到 v25 迁移指南Delegate 参数对象全面转向 init 初始化器CsvHelper v24 到 v25 迁移指南Delegate 参数对象全面转向 init 初始化器 本文是 CsvHelper 官方迁移文档中「从版本 2示例工程LeetCode 135 Candy 发糖问题全解从暴力修正到 O(n) 单遍贪心多语言实现LeetCode 135 Candy 发糖问题全解从暴力修正到 O n 单遍贪心多语言实现 本文以 LeetCode 135 号题 Candy分发糖示例工程LeetCode-Go 题解 135Candy 分发糖果的双向贪心扫描算法深度解析LeetCode Go 题解 135Candy 分发糖果的双向贪心扫描算法深度解析 导读 LeetCode 第 135 题「Candy分发糖果」是一道经示例工程上一篇darktable免费开源的 RAW 照片处理器从导入到导出的完整指南下一篇SQLModel FastAPI 应用测试用内存 SQLite 与 StaticPool 搭建隔离、可复用的测试数据库创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/17 1:49:42

AUTOSAR实验:基于C/S Port实现ADC电压读取与RTE调用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 1:49:42

双串锂电硬件均衡设计:MP2642GR-Z与STM32L151ZD协同实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 2:49:45

初识C语言(mac版)

个人学习的心得记录和知识梳理1. C语言是什么?C语言是一门计算机语言,是人和计算机交流的语言。 人和人交流使用的是自然语言,如:汉语、英语、日语。 那人和计算机是怎么交流的呢?使用计算机语言; 人是通过计算机语言写的程序,给计算…

2026/10/17 2:49:45

面试官问:AI十分钟能写完的代码,你为什么要学五年?

摘要:2026年9月,一位5年经验的Java工程师在大厂终面被问:“你简历上这些技术栈,AI十分钟就能写出生产级代码。你花五年学的东西,价值到底在哪?”他答不上来。评论区几百条回复几乎全是同一个意思&#xff1…

2026/10/17 2:44:45

Python第五天

文件操作打开模式与文件关闭操作文件通常分为三个步骤:打开文件、读取或写入、关闭文件。先用 open() 完成基本操作,再使用 with 简化资源管理。使用open打开文件open() 用于打开文件,并返回文件对象。通过文件对象可以读写内容,操…

2026/10/16 5:33:43

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/16 4:03:48

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/15 13:42:16

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/17 0:04:32

PlanarSLAM:面向曼哈顿世界的点线面联合SLAM系统

简介:本资源是面向机器人视觉与SLAM研究者的C开源RGB-D SLAM系统实现,专为结构化室内场景(如办公室、走廊)设计,通过融合点、线、平面三类几何特征并引入“曼哈顿世界”假设,显著提升位姿跟踪鲁棒性与稠密地…

2026/10/17 0:04:32

TDH Workflow调度实战:从DAG依赖驱动到任务编排与故障排查

1. 从一个调度脚本说起:TDH Workflow到底在解决什么问题第一次接触TDH的Workflow,是在一个数据清洗项目里。当时团队每天要跑几十个ETL任务,任务之间有严格的先后依赖——A表清洗完才能跑B表的关联,B表跑完才能触发C表的聚合。最开…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑