当前位置: 首页 > news >正文

LeetCode 135.分发糖果:双向遍历下的贪心策略应用

一、问题定义与核心约束

1.1 问题描述

n 个孩子站成一排,每个孩子都有一个评分 ratings。需要按照以下规则给孩子们分发糖果:

  • 每个孩子至少分配到 1 个糖果。
  • 相邻两个孩子中,评分高的孩子必须获得更多的糖果。

要求返回最少需要的糖果总数。

1.2 核心挑战

  • 单向约束可能导致冲突:例如,当一个孩子同时比左边和右边的孩子评分高时,需同时满足两边的糖果数量要求
  • 需找到满足所有约束的最小糖果分配方案,避免过度分配

示例

  • 输入:ratings = [1,0,2]
  • 输出:5(分配方案:[2,1,2]

二、解题思路:双向贪心的分步满足策略

2.1 核心思想

通过两次遍历分别满足左右方向的约束,最终取两次约束的最大值作为每个孩子的糖果数:

  1. 左→右遍历:确保每个孩子比左边评分高的孩子获得更多糖果
  2. 右→左遍历:确保每个孩子比右边评分高的孩子获得更多糖果
  3. 每个孩子的最终糖果数为两次遍历结果的最大值,同时满足两个方向的约束

2.2 为什么需要双向遍历?

  • 单向遍历无法处理"同时比左右邻居评分高"的情况(如 [1,3,2]
  • 分两次遍历可分别固定一个方向的参考基准,确保被比较方的糖果数量已确定

三、代码逐行解析

3.1 初始化糖果数组

int[] candies = new int[ratings.length];
Arrays.fill(candies, 1);  // 每个孩子至少1个糖果
  • 初始值设为1,满足"每个孩子至少1个糖果"的基本约束

3.2 左→右遍历:满足右侧比左侧评分高的约束

for (int i = 1; i < candies.length; i++) {// 若当前孩子评分高于左边,糖果数比左边多1if (ratings[i] > ratings[i - 1]) {candies[i] = candies[i - 1] + 1;}
}
  • 关键逻辑:此时左边孩子的糖果数已确定(因从左向右遍历),可直接作为参考基准
  • 示例:ratings = [1,0,2] 经左→右遍历后,candies = [1,1,2]

3.3 右→左遍历:满足左侧比右侧评分高的约束

for (int i = candies.length - 2; i >= 0; i--) {// 若当前孩子评分高于右边,取"当前值"与"右边+1"的最大值if (ratings[i] > ratings[i + 1]) {candies[i] = Math.max(candies[i], candies[i + 1] + 1);}
}
  • 关键逻辑:此时右边孩子的糖果数已确定(因从右向左遍历),通过 Math.max 确保同时满足左→右遍历的结果
  • 示例:ratings = [1,0,2] 经右→左遍历后,candies = [2,1,2](处理了 ratings[0] > ratings[1] 的约束)

3.4 计算总糖果数

return Arrays.stream(candies).sum();  // 返回所有糖果的总和

四、算法执行过程演示

ratings = [1,3,2,1] 为例:

步骤1:初始化

candies = [1,1,1,1]

步骤2:左→右遍历
  • i=1ratings[1]=3 > ratings[0]=1candies[1] = 1+1=2[1,2,1,1]
  • i=2ratings[2]=2 < ratings[1]=3 → 不变 → [1,2,1,1]
  • i=3ratings[3]=1 < ratings[2]=2 → 不变 → [1,2,1,1]
步骤3:右→左遍历
  • i=2ratings[2]=2 > ratings[3]=1max(1, 1+1)=2[1,2,2,1]
  • i=1ratings[1]=3 > ratings[2]=2max(2, 2+1)=3[1,3,2,1]
  • i=0ratings[0]=1 < ratings[1]=3 → 不变 → [1,3,2,1]
步骤4:总和计算

1+3+2+1=7 → 最终结果为 7

五、核心逻辑深度解析

5.1 为什么被比较方的糖果数必须已确定?

  • 左→右遍历时:比较对象是左边的孩子(i-1),由于遍历顺序是从左到右,i-1 的糖果数在之前的步骤中已确定,可直接作为参考
  • 右→左遍历时:比较对象是右边的孩子(i+1),由于遍历顺序是从右到左,i+1 的糖果数在之前的步骤中已确定,可直接作为参考
  • 这种"已确定状态"保证了每次赋值都是基于可靠的基准,避免冲突

5.2 为什么要用 Math.max 处理右→左遍历?

  • 左→右遍历可能已为当前位置赋予了一个满足左侧约束的值(如 candies[i] 可能已经大于 candies[i+1]+1
  • 例如:ratings = [5,4,3,6] 左→右遍历后 candies = [1,1,1,2],右→左遍历到 i=2 时,ratings[2]=3 < ratings[3]=6 无需处理;遍历到 i=0 时,ratings[0]=5 > ratings[1]=4,但 candies[0]=1 需更新为 max(1, 1+1)=2

5.3 贪心策略的体现

  • 每次遍历只关注一个方向的约束,确保局部最优(满足当前方向的最小糖果数)
  • 最终通过取最大值合并两个方向的约束,实现全局最优(总糖果数最少)

六、算法复杂度分析

  • 时间复杂度O(n),其中 n 是孩子数量。两次遍历数组各需 O(n) 时间,求和操作也为 O(n)
  • 空间复杂度O(n),需要一个长度为 n 的数组存储每个孩子的糖果数

七、常见误区与优化说明

7.1 误区1:试图通过一次遍历解决

单向遍历无法处理"同时需要比左右邻居多"的情况,例如 ratings = [2,1,3]

  • 左→右遍历会得到 [1,1,2],但 ratings[0] > ratings[1] 未满足
  • 必须通过右→左遍历补充处理

7.2 误区2:忽略 Math.max 的作用

右→左遍历时直接赋值 candies[i] = candies[i+1] + 1 会覆盖左→右遍历的结果,可能破坏已满足的左侧约束。

7.3 优化点:空间复杂度优化

可通过两个变量替代数组(分别记录当前和前一个位置的糖果数),将空间复杂度降至 O(1),但会增加逻辑复杂度。

八、总结:双向约束问题的解题范式

本题通过分阶段处理双向约束的思路,为类似问题提供了通用解法:

  1. 识别问题中的双向约束(如本题中"左高右低"和"右高左低"两种情况)
  2. 分两次遍历,每次处理一个方向的约束,确保被比较方的状态已确定
  3. 合并两次遍历的结果,取满足所有约束的最小值

这种思路的核心是将复杂的全局约束分解为可分步处理的局部约束,通过贪心策略实现高效求解。

http://www.dtcms.com/a/339319.html

相关文章:

  • Kubernetes Pod 控制器
  • Effective C++ 条款50:了解new和delete的合理替换时机
  • 实践项目-1
  • jenkins自动化部署
  • 七十二、【Linux数据库】MySQL数据库MHA集群概述 、 部署MHA集群
  • 当MySQL的int不够用了
  • GTSAM中实现多机器人位姿图优化(multi-robot pose graph optimization)示例
  • 权限管理系统
  • 动手学深度学习(pytorch版):第四章节—多层感知机(7、8)数值稳定性和模型初始化
  • 《算法导论》第 31 章 - 数论算法
  • 个人介绍CSDNmjhcsp
  • Kubernetes集群安装部署--flannel
  • Vue 2 项目中快速集成 Jest 单元测试(超详细教程)
  • 云计算学习100天-第23天
  • github 上传代码步骤
  • 【Python】新手入门:python模块是什么?python模块有什么作用?什么是python包?
  • Day13_【DataFrame数据组合merge连接】【案例】
  • 嵌入式开发学习———Linux环境下网络编程学习(三)
  • 第5.5节:awk算术运算
  • RabbitMQ:交换机(Exchange)
  • LeetCode-17day:贪心算法
  • 95、23种设计模式之建造者模式(4/23)
  • 大模型 + 垂直场景:搜索/推荐/营销/客服领域开发新范式与技术实践
  • 抓取手机游戏相关数据
  • 细化的 Spring Boot 和 Spring Framework 版本对应关系
  • c++计算器(简陋版)
  • 【全面推导】策略梯度算法:公式、偏差方差与进化
  • 差分(附带例题题解)
  • 深度学习 --- 基于ResNet50的野外可食用鲜花分类项目代码
  • 基于单片机身体健康监测/身体参数测量/心率血氧血压