每日一算:分发糖果

         在算法面试中,“分发糖果” 是一道经典的贪心算法应用题,核心考察对 “局部最优推导全局最优” 的理解。本文将从问题分析出发,提供两种主流解题思路,并附上 C++ 实现代码,帮助你彻底掌握这道题。

一、问题重述

题目要求

有 n 个孩子站成一排,每个孩子有评分 ratings[i],分发糖果需满足:

  1. 每个孩子至少得 1 颗糖果;
  2. 相邻孩子中,评分高的孩子必须获得更多糖果。
    求最少需要准备的糖果总数。

示例理解

  • 示例 1:ratings = [1,0,2]
    分发方案 [2,1,2],总数 5
    解释:第二个孩子评分最低(0)得 1 颗,第一个比第二个高(1>0)得 2 颗,第三个比第二个高(2>0)得 2 颗。
  • 示例 2:ratings = [1,2,2]
    分发方案 [1,2,1],总数 4
    解释:第三个孩子与第二个评分相同(2=2),无需更多糖果,故得 1 颗(满足 “至少 1 颗” 即可)。

二、解题思路

核心难点

相邻关系是 “双向约束”:既要保证 “左→右” 方向评分高的糖果多,也要保证 “右→左” 方向评分高的糖果多。若只单向处理,会导致另一侧约束被破坏。

思路 1:两次遍历(贪心经典解法)

原理

贪心算法的核心是 “分阶段满足约束”:

  1. 左→右遍历:保证每个孩子比左边评分高时,糖果数比左边多(不考虑右边);
  2. 右→左遍历:保证每个孩子比右边评分高时,糖果数比右边多(此时需结合左→右的结果,取最大值,避免破坏左侧约束)。
步骤拆解
  1. 初始化数组 candies,所有元素为 1(满足 “至少 1 颗”);
  2. 左→右遍历(处理 “左边约束”):
    若 ratings[i] > ratings[i-1],则 candies[i] = candies[i-1] + 1
  3. 右→左遍历(处理 “右边约束”):
    若 ratings[i] > ratings[i+1],则 candies[i] = max(candies[i], candies[i+1] + 1)
  4. 求和 candies 数组,得到最少糖果总数。
示例验证(以 ratings = [1,0,2] 为例)
  • 初始化:candies = [1,1,1]
  • 左→右遍历:
    i=1(0 <1):无变化;i=2(2> 0):candies[2] = 1+1=2 → 此时 [1,1,2]
  • 右→左遍历:
    i=1(0 <2):无变化;i=0(1> 0):candies[0] = max(1, 1+1)=2 → 最终 [2,1,2]
  • 求和:2+1+2=5(正确)

思路 2:一次遍历(优化空间,进阶解法)

原理

观察到 “糖果数的变化与评分的递增 / 递减序列相关”,可通过记录当前递增 / 递减序列的长度,动态计算糖果数,无需额外存储 candies 数组(空间复杂度从 O (n) 降至 O (1))。

关键概念
  • up:当前递增序列的长度(评分持续上升时,每多一个孩子,糖果数 + 1);
  • down:当前递减序列的长度(评分持续下降时,每多一个孩子,糖果数需回溯调整,避免重复计算);
  • pre:前一个孩子的糖果数(动态更新)。
步骤拆解
  1. 初始化 result=1(第一个孩子至少 1 颗)、up=1down=0pre=1
  2. 从第二个孩子开始遍历:
    • 若 ratings[i] > ratings[i-1](递增):
      up = pre + 1down=0pre=upresult += up
    • 若 ratings[i] == ratings[i-1](相等):
      up=1down=0pre=1result += 1(相等时无需更多糖果,取 1 即可);
    • 若 ratings[i] < ratings[i-1](递减):
      down += 1up=1
      若 down == pre(递减序列长度等于前一个糖果数,需补 1 避免冲突):result += 1
      result += downpre=1(递减序列中当前孩子糖果数为 1,前一个需回溯调整);
  3. 遍历结束,result 即为最少糖果总数。
示例验证(以 ratings = [1,2,2] 为例)
  • 初始:result=1up=1down=0pre=1
  • i=1(2>1,递增):
    up=2pre=2result=1+2=3
  • i=2(2=2,相等):
    pre=1result=3+1=4(正确)

三、C++ 代码实现

实现 1:两次遍历(易理解,推荐面试首选)

#include <iostream>
#include <vector>
#include <algorithm> // for max()using namespace std;class Solution {
public:int candy(vector<int>& ratings) {int n = ratings.size();if (n == 0) return 0;// 初始化:每个孩子至少1颗糖果vector<int> candies(n, 1);// 左→右遍历:处理左边约束(比左边高则+1)for (int i = 1; i < n; ++i) {if (ratings[i] > ratings[i-1]) {candies[i] = candies[i-1] + 1;}}// 右→左遍历:处理右边约束(比右边高则取max(当前, 右边+1))for (int i = n-2; i >= 0; --i) {if (ratings[i] > ratings[i+1]) {candies[i] = max(candies[i], candies[i+1] + 1);}}// 求和int total = 0;for (int num : candies) {total += num;}return total;}
};// 测试代码
int main() {Solution sol;vector<int> ratings1 = {1,0,2};cout << "示例1最少糖果数:" << sol.candy(ratings1) << endl; // 输出5vector<int> ratings2 = {1,2,2};cout << "示例2最少糖果数:" << sol.candy(ratings2) << endl; // 输出4return 0;
}

实现 2:一次遍历(空间优化,进阶)

#include <iostream>
#include <vector>using namespace std;class Solution {
public:int candy(vector<int>& ratings) {int n = ratings.size();if (n == 0) return 0;if (n == 1) return 1;int result = 1;  // 第一个孩子的1颗糖果int up = 1;      // 当前递增序列长度int down = 0;    // 当前递减序列长度int pre = 1;     // 前一个孩子的糖果数for (int i = 1; i < n; ++i) {if (ratings[i] > ratings[i-1]) {// 递增序列up = pre + 1;down = 0;pre = up;result += up;} else if (ratings[i] == ratings[i-1]) {// 相等:当前孩子取1颗,重置状态up = 1;down = 0;pre = 1;result += 1;} else {// 递减序列down += 1;up = 1;// 若递减长度等于前一个糖果数,需补1(避免冲突)if (down == pre) {down += 1;}result += down;pre = 1;  // 递减序列中当前孩子糖果数为1}}return result;}
};// 测试代码
int main() {Solution sol;vector<int> ratings1 = {1,0,2};cout << "示例1最少糖果数:" << sol.candy(ratings1) << endl; // 输出5vector<int> ratings2 = {1,2,2};cout << "示例2最少糖果数:" << sol.candy(ratings2) << endl; // 输出4return 0;
}

四、复杂度分析

解法时间复杂度空间复杂度适用场景
两次遍历O(n)O(n)面试首选,易理解、易调试
一次遍历O(n)O(1)空间敏感场景(如大数据量)

五、总结

  1. 两次遍历解法的核心是 “分阶段满足双向约束”,通过两次单向遍历覆盖所有相邻关系,逻辑清晰,适合面试中快速实现;
  2. 一次遍历解法通过动态记录序列长度优化空间,需要对 “递减序列的回溯调整” 有深入理解,适合进阶学习;
  3. 无论哪种解法,都遵循贪心算法的 “局部最优→全局最优” 思想:每次只处理当前能确定的最优解,最终累积得到全局最少糖果数。

建议先掌握两次遍历解法,再尝试理解一次遍历的优化逻辑,逐步提升对贪心算法的应用能力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/98705.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/98705.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【WorkManager】无法在 Direct Boot 模式下初始化

【WorkManager】无法在 Direct Boot 模式下初始化一、问题描述二、问题分析2.1 关于 Direct Boot 模式2.2 支持 Direct Boot 模式2.3 手动初始化 WorkManager 组件2.4 WorkManager 不支持 Direct Boot 的官方修改三、解决方案一、问题描述 在使用 WorkManager 库来实现开机上报…

Hybrid应用性能优化实战分享(本文iOS 与 H5为例,安卓同理)

前言在移动应用开发中&#xff0c;Hybrid 架构因其跨平台特性和开发效率优势被广泛采用。然而&#xff0c;WebView 的性能问题一直是开发者面临的挑战。本文将基于实际项目经验&#xff0c;分享 iOS Hybrid 应用的核心优化策略&#xff0c;涵盖 WebView 池化、预加载、用户体验…

点积、叉积、矩阵行列式详解、线性相关与线性无关、矩阵的秩、矩阵可逆与不可逆详解

1.向量 1.1 点积&#xff08;Dot Product&#xff09; 1.1.1 定义 点积是在求一个标量&#xff0c;点积结果没有方向。 对于两个向量u(u1,u2,u3),v(v1,v2,v3)\bold{u}(u_1,u_2,u_3),\bold{v}(v_1,v_2,v_3)u(u1​,u2​,u3​),v(v1​,v2​,v3​) 点积定义为&#xff1a;u⋅vu1v1u…

Mac安装nvm详细教程(超简单)

本章教程,主要介绍如何在Mac操作系统上安装nvm. 我们使用官方一键安装脚本,完成安装 一、安装步骤 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash配置环境变量,编辑.zshrc文件 vim .zshrcexport NVM_DIR="$(

【selenium】网页元素找不到?从$(‘[placeholder=“手机号“]‘)说起

网页元素找不到&#xff1f;从$(‘[placeholder“手机号”]’)说起总结&#xff1a;控制台不骗人&#xff0c;元素选不到&#xff0c;八成是写法、时机或环境的问题。我们在写网页自动化脚本或者调试页面的时候&#xff0c;经常遇到一个让人头疼的问题&#xff1a;明明元素就在…

SSE 模仿 GPT 响应

后端代码 const express require(express) const cors require(cors);const app express(); app.use(cors()); const port 3000;app.listen(port, () > {console.log(Server running at http://localhost:${port}/); });const msg 全国同胞们&#xff0c; 尊敬的各位国…

MAC 多个版本 JDK进行切换

1.查看本机所有的jdk/usr/libexec/java_home -V2、打开bash_profile文件。可以在终端vim ~/.bash_profile打开&#xff0c;也可以打开访达shiftcmdG然后输入/Users/mac/.bash_profile&#xff08;本机bash_profile的路径&#xff09;加入新的环境变量格式如下&#xff08;参考我…

shell 中 expect 详解

一、概述Expect是一个免费的编程工具语言&#xff0c;用来实现自动和交互式任务进行通信&#xff0c;而无需人的干预。Expect的作者DonLibes在1990年开始编写Expect时对Expect做有如下定义&#xff1a;Expect是一个用来实现自动交互功能的软件套件。通过expect系统管理员可以创…

第4讲 机器学习基础概念

机器学习作为人工智能的子领域&#xff0c;专注于训练计算机算法自动发现数据中的模式与关联关系。以下是其核心基础概念&#xff1a;4.1 数据数据是机器学习的基石。缺乏数据&#xff0c;算法将无从学习。数据可呈现为结构化数据&#xff08;如电子表格、数据库&#xff09;和…

Go组合式继承:灵活替代方案

Go 语言没有传统面向对象编程中的继承机制&#xff0c;但通过组合和接口实现类似功能。Go 更提倡组合优于继承的设计原则&#xff0c;这种设计方式更灵活且易于维护。结构体组合&#xff08;伪继承&#xff09;通过嵌套结构体实现类似继承的效果。子结构体可以直接访问父结构体…

Verilog三段式FSM,实现十字路口红绿灯

运行环境&#xff1a;VCS verdi状态说明&#xff1a;S0 &#xff1a; 初始状态 S1 &#xff1a; 东西方向绿灯亮&#xff0c;南北方向红灯亮&#xff1b;点亮30周期 S2 &#xff1a; 东西方向黄灯亮&#xff0c;南北方向红灯亮&#xff1b;点亮2 周期 S3 &#xff1a; 东西方向…

java 将pdf转图片

如何将pdf文件转为图片 import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.rendering.PDFRenderer; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; public class Pdf2Png {/**…

手搓Spring

目录 两种方法创建Spring容器 自定义Spring容器及前置操作 Spring扫描逻辑实现 createBean()方法 getBean()方法 依赖注入&#xff08;DI&#xff09; BeanNameAware接口 InitializingBean接口 BeanPostProcessor接口 AOP的实现 Spring 是一个轻量级的 Java 开发框架…

.NET 单文件程序详解:从原理到实践

C# 混淆加密大师在最新版本中, 提供了.NET单文件解包打包功能, 它可以快速解包官方打包的单文件程序&#xff0c;恢复为原始的多文件结构。也可以对解包后的程序集进行混淆与加密&#xff0c;有效提升逆向门槛。最后还能重新打包成单文件程序&#xff0c;保持对用户友好的分发形…

Spring面试题记录?

请简述 Spring 框架的核心是什么&#xff1f;它主要包含了哪些核心模块&#xff1f; spring的核心模块主要有spring-core&#xff08;工具类&#xff0c;资源加载&#xff09;&#xff0c;spring-bean&#xff08;bean的定义&#xff0c;创建&#xff0c;封装&#xff09;&…

一次缓存引发的文件系统数据不一致问题排查与深度解析

01 起因EFC&#xff08;Elastic File Client&#xff09;是 NAS 自研的分布式文件系统客户端&#xff0c;最近完成了对缓存架构的更新&#xff0c;现在支持多个客户端之间构成分布式缓存&#xff0c;底层支持 NAS、CPFS 和 OSS。由于开发时间较短&#xff0c;一直没有做 NAS 场…

Spring Boot Gateway 教程:从入门到精通

一、Spring Cloud Gateway 简介Spring Cloud Gateway 是基于 Spring 5、Project Reactor 和 Spring Boot 2 构建的 API 网关&#xff0c;旨在为微服务架构提供一种简单而有效的路由管理方式。它取代了 Netflix Zuul&#xff0c;提供了更高效和更强大的网关解决方案。核心特点&a…

防火墙 只允许信任的几台服务器访问

1. 首先&#xff0c;确保 firewalld 服务正在运行&#xff1a;systemctl start firewalld systemctl enable firewall2. 设置默认拒绝规则&#xff1a;设置默认拒绝所有流量&#xff08;拒绝所有的入站流量&#xff09;&#xff1a;firewall-cmd --zonepublic --add-rejectal…

十三,数据结构-树

定义树也是基于节点的数据结构&#xff0c;和链表不同的是&#xff0c;树的节点可以指向多个节点。首先对树的一些常用术语进行说明&#xff1a;最上面的节点叫做根节点&#xff0c;根位于树顶&#xff0c;如图中的节点A&#xff1b;和族谱一样&#xff0c;节点有后代和祖先&am…

JVM-默背版

1.JVM对sychronized的优化&#xff1a;锁膨胀、锁消除、锁粗化、自适应自旋锁 &#xff08;1&#xff09;锁膨胀&#xff1a;从无锁、偏向锁、轻量级锁、重量级锁的过程叫做锁膨胀。在JDK1.6以前&#xff0c;sychronized是由重量级锁实现的&#xff0c;加锁和解锁的过程需要从用…