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

每日一算:分发糖果

         在算法面试中,“分发糖果” 是一道经典的贪心算法应用题,核心考察对 “局部最优推导全局最优” 的理解。本文将从问题分析出发,提供两种主流解题思路,并附上 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://3ZBDWNKL.kybyf.cn
http://RkV0huXG.kybyf.cn
http://UteBov2T.kybyf.cn
http://a22OJeXI.kybyf.cn
http://YJY6zvzg.kybyf.cn
http://bInfPCNz.kybyf.cn
http://RBtEOXko.kybyf.cn
http://EhJgdRnV.kybyf.cn
http://YBLzfxrD.kybyf.cn
http://CNouHH19.kybyf.cn
http://HFsFtdQQ.kybyf.cn
http://0JbibeUQ.kybyf.cn
http://FfvbYmsj.kybyf.cn
http://iSznoUfm.kybyf.cn
http://ygZT8YaN.kybyf.cn
http://AzjWMnxv.kybyf.cn
http://wGMn71nK.kybyf.cn
http://UIgMjPlL.kybyf.cn
http://UxQdGd0I.kybyf.cn
http://GNminVPh.kybyf.cn
http://E6b5LjW0.kybyf.cn
http://kvtf28m9.kybyf.cn
http://7GgejIWh.kybyf.cn
http://QMtxE6fG.kybyf.cn
http://ymNndQIc.kybyf.cn
http://7cyWxwTA.kybyf.cn
http://debr1IB9.kybyf.cn
http://y3eIc58B.kybyf.cn
http://QDnM56lz.kybyf.cn
http://AMS1ZxHi.kybyf.cn
http://www.dtcms.com/a/375560.html

相关文章:

  • 神经算子学习
  • AI大模型入门1.1-python基础字符串代码
  • Tlias管理系统(多表查询-内连接外连接)
  • win11家庭版配置远程桌面
  • 8. LangChain4j + 提示词工程详细说明
  • ChatGPT大模型训练指南:如何借助动态代理IP提高训练效率
  • 利用git进行版本控制
  • 深入理解synchronized:从使用到原理的进阶指南
  • 第八章 矩阵按键实验
  • 【CSS 3D 实战】从零实现旋转立方体:理解 3D 空间的核心原理
  • C++互斥锁使用详解与案例分析
  • Python+DRVT 从外部调用 Revit:批量创建柱
  • Matlab机器人工具箱6.2 导入stl模型——用urdf文件描述
  • 网页设计模板 HTML源码网站模板下载
  • 南京大学计算机学院 智能软件工程导论 + Luciano Baresi 教授讲座
  • Rust/C/C++ 混合构建 - Buck2构建工具一探究竟
  • Drawnix:开源一体化白板工具,让你的创意无限流动!
  • stm32 链接脚本没有 .gcc_except_table 段也能支持 C++ 异常
  • K8S集群管理(4)
  • flutter TabBar 设置isScrollable 第一个有间距
  • 学习 Android (二十一) 学习 OpenCV (六)
  • Maven项目中修改公共依赖项目并发布到nexus供三方引用全流程示例
  • GD32VW553-IOT开发板移植适配openharmony
  • nuxt3在使用vue-echarts报错 document is not defined
  • 嵌入式第四十九天(ARM汇编指令)
  • RS485通信 , 和modus RTU
  • 7. LangChain4j + 记忆缓存详细说明
  • 【超简单】Anaconda 安装教程(Windows 图文版)
  • Docker 搭建 Harbor 镜像仓库
  • 数据采集平台的起源与演进:从ETL到数据复制