糖果大冒险:公平分发的智慧挑战
🌟《糖果分发大冒险:智慧守护者的挑战》
在一个遥远而美丽的小镇上,住着一群可爱的孩子。每年一度的“甜蜜节”即将到来,镇长决定为每个孩子分发糖果。但是,为了鼓励孩子们努力学习和表现自己,镇长制定了一个特别的规则:
评分高的孩子必须比相邻的孩子获得更多糖果。
作为镇上的智慧守护者——你,被委以重任,需要设计一种公平且高效的糖果分配方案,确保每个孩子都能得到应得的糖果,并且总数尽可能少。
📖 故事背景
假设现在有 n
个孩子站成一排,每个孩子都有一个评分 ratings[i]
,表示他们在过去一年中的综合表现。你的任务是根据这些评分,给每个孩子分发最少数量的糖果,同时满足以下条件:
每个孩子至少有一颗糖果。
如果某个孩子的评分比相邻的孩子高,则该孩子必须获得更多的糖果。
🧠 解题思路:两次遍历法
核心思想
我们可以将问题拆解为两个单向规则来简化处理:
左规则:从左到右遍历,如果当前孩子的评分比左边的孩子高,则他应该比左边的孩子多一颗糖果。
右规则:从右到左遍历,如果当前孩子的评分比右边的孩子高,则他应该比右边的孩子多一颗糖果。
最终,每个孩子的糖果数应该是这两个规则中较大值的组合。
实现步骤
初始化:
创建一个数组
left
,用于存储每个孩子根据左规则所需的糖果数。使用一个变量
right
动态记录右规则下的糖果数。
左规则遍历:
从左到右遍历评分数组,如果当前孩子的评分高于前一个孩子,则给他比前一个多一颗糖果;否则,给他一颗糖果(最小值)。
右规则遍历:
从右到左遍历评分数组,维护一个递增链
right
。如果当前孩子的评分高于右边的孩子,则增加right
值;否则,重置right
为 1。对于每个孩子,计算其最终糖果数为
max(left[i], right)
。
累加求和:
将所有孩子的最终糖果数相加,得到最少需要准备的糖果总数。
💻 完整代码实现
#include <iostream>#include <vector>#include <algorithm>using namespace std;/*** @brief 计算最少需要准备的糖果总数* @param ratings 孩子们的评分数组* @return 最少糖果总数*/int candy(vector<int>& ratings) {int n = ratings.size();// Step 1: 初始化 left 数组,每个孩子至少一颗糖果vector<int> left(n, 1);// Step 2: 左规则遍历,从左到右for (int i = 1; i < n; ++i) {if (ratings[i] > ratings[i - 1]) {left[i] = left[i - 1] + 1;}}// Step 3: 右规则遍历,从右到左int right = 1;int totalCandies = 0;for (int i = n - 1; i >= 0; --i) {if (i < n - 1 && ratings[i] > ratings[i + 1]) {// 当前孩子的评分比右边高,递增链继续right++;} else {// 递增链断开,重置 right 为 1right = 1;}// 最终糖果数取左右规则的最大值totalCandies += max(left[i], right);}return totalCandies;}// 测试主函数int main() {// 示例输入vector<int> ratings = {1, 0, 2};cout << "孩子们的评分: ";for (int rating : ratings) {cout << rating << " ";}cout << endl;int result = candy(ratings);cout << "最少需要准备的糖果总数: " << result << endl; // 输出: 5return 0;}
📊 复杂度分析
项目 | 时间复杂度 | 空间复杂度 |
---|---|---|
整体 | O(n) —— 两次遍历数组 | O(n) —— 需要额外的 left 数组 |
🎯 关键点解析
1. 左规则遍历
在第一次遍历时,我们只考虑每个孩子相对于左边的情况:
如果
ratings[i] > ratings[i-1]
,则left[i] = left[i-1] + 1
;否则,
left[i] = 1
(每个孩子至少有一颗糖果)。
这一步保证了每个孩子在其左侧邻居评分低于自己的情况下,能够获得足够的糖果。
2. 右规则遍历
在第二次遍历时,我们从右向左遍历数组,动态维护一个递增链 right
:
如果
ratings[i] > ratings[i+1]
,则递增链继续,right++
;否则,递增链断开,
right = 1
。
这一步确保了每个孩子在其右侧邻居评分低于自己的情况下,也能获得足够的糖果。
3. 最终糖果数
对于每个孩子,其最终糖果数为 max(left[i], right)
,这样可以同时满足左右两个方向的要求。
🎨 图解示例
假设 ratings = [1, 0, 2]
:
左规则遍历结果:
索引 | 评分 | 左规则糖果数 |
---|---|---|
0 | 1 | 1 |
1 | 0 | 1 |
2 | 2 | 2 |
右规则遍历过程:
从右向左:
i=2:
right = 1
i=1:
ratings[1] < ratings[2]
→right = 2
i=0:
ratings[0] > ratings[1]
→right = 1
最终糖果数:
索引 | 评分 | 左规则糖果数 | 右规则糖果数 | 最终糖果数 |
---|---|---|---|---|
0 | 1 | 1 | 1 | 1 |
1 | 0 | 1 | 2 | 2 |
2 | 2 | 2 | 1 | 2 |
总糖果数 = 1 + 2 + 2 = 5
🚨 防止越界的具体实现
为了更清晰地理解如何防止越界,让我们再看一个极端的例子:
假设 ratings = [5]
,只有一个孩子:
1. 初始化
vector<int> left(1, 1); // 左规则数组,初始值为1int right = 1; // 右规则变量,初始值为1int totalCandies = 0; // 总糖果数
2. 第一次遍历(从左到右)
索引 | 评分 | 左规则糖果数 |
---|---|---|
0 | 5 | 1 |
3. 第二次遍历(从右到左)
i = 0
:i < n - 1
不成立(因为i == 0
),直接进入else
分支。right = 1
totalCandies += max(left[0], right) = max(1, 1) = 1
4. 总糖果数
totalCandies = 1
📝 关键总结
防止越界的关键在于条件
i < n - 1
:这个条件确保了只有当
i
不是最后一个元素时,才会进行ratings[i + 1]
的比较。如果
i == n - 1
,则直接进入else
分支,避免访问越界的ratings[i + 1]
。
对于最后一个元素:
最后一个元素没有右边的孩子,因此我们不需要比较它的评分与右边的孩子。
直接将
right
设为 1,表示该孩子至少有一颗糖果。
通过这种方式,我们能够安全地处理数组的边界情况,确保算法的正确性和稳定性。
🎯 总结
主要收获
分解问题:将复杂的双向约束分解为两个单向约束,分别处理。
贪心策略:每次尽量给出最小的满足条件的糖果数,最后合并两个方向的结果。
优化空间:虽然这里用了额外的
left
数组,但也可以通过一些技巧进一步优化空间复杂度。
具体实现步骤
初始化:创建
left
数组和right
变量。左规则遍历:从左到右遍历,更新
left
数组。右规则遍历:从右到左遍历,动态维护
right
,并计算最终糖果数。累加求和:计算总糖果数。
代码实现
#include <iostream>#include <vector>#include <algorithm>using namespace std;int candy(vector<int>& ratings) {int n = ratings.size();// Step 1: 初始化 left 数组,每个孩子至少一颗糖果vector<int> left(n, 1);// Step 2: 左规则遍历,从左到右for (int i = 1; i < n; ++i) {if (ratings[i] > ratings[i - 1]) {left[i] = left[i - 1] + 1;}}// Step 3: 右规则遍历,从右到左int right = 1;int totalCandies = 0;for (int i = n - 1; i >= 0; --i) {if (i < n - 1 && ratings[i] > ratings[i + 1]) {// 当前孩子的评分比右边高,递增链继续right++;} else {// 递增链断开,重置 right 为 1right = 1;}// 最终糖果数取左右规则的最大值totalCandies += max(left[i], right);}return totalCandies;}// 测试主函数int main() {vector<int> ratings = {1, 0, 2};cout << "孩子们的评分: ";for (int rating : ratings) {cout << rating << " ";}cout << endl;int result = candy(ratings);cout << "最少需要准备的糖果总数: " << result << endl; // 输出: 5return 0;}
希望这个故事能帮助你更好地理解并掌握「分发糖果」问题的核心思想!如果你有任何疑问或需要进一步的帮助,请随时告诉我! 😊