1. 题目描述
LeetCode 第 135 题是“分发糖果”(Candy)。题目描述为:有 n
个孩子站成一排,每个孩子有一个评分值。你需要按照以下要求给这些孩子分发糖果:
- 每个孩子至少分配到 1 个糖果。
- 相邻的孩子中,评分高的孩子必须获得更多的糖果。
求最少需要准备多少颗糖果。
2. 模式识别
本题可通过贪心算法来解决。贪心算法的核心思想是在每一步都做出当前看起来最优的选择,从而希望最终得到全局最优解。在本题中,我们可以通过两次遍历数组,分别考虑每个孩子与其左边和右边孩子的评分关系,来确定每个孩子应得的最少糖果数。
3. 考点分析
- 贪心算法思想:理解并运用贪心策略,在局部做出最优选择以达到全局最优。
- 数组遍历与条件判断:需要对数组进行多次遍历,并根据相邻元素的大小关系进行条件判断和更新。
- 空间优化:在满足题目要求的前提下,考虑如何优化空间复杂度。
4. 解法
- 两次遍历法:
- 第一次从左到右遍历数组,如果当前孩子的评分比左边孩子高,则当前孩子的糖果数比左边孩子多 1。
- 第二次从右到左遍历数组,如果当前孩子的评分比右边孩子高,且当前孩子的糖果数不大于右边孩子的糖果数,则更新当前孩子的糖果数为右边孩子的糖果数加 1。
- 最后将所有孩子的糖果数相加得到结果。
- 常数空间优化法:通过一次遍历,记录上升和下降序列的长度,根据序列长度计算糖果数,可将空间复杂度优化到 O ( 1 ) O(1) O(1)。
5. 最优解法(两次遍历法)的 C 语言代码
#include <stdio.h>
#include <stdlib.h>
// 计算最少需要的糖果数
// ratings: 孩子的评分数组
// ratingsSize: 孩子的数量
// 返回值: 最少需要的糖果数
int candy(int* ratings, int ratingsSize) {
// 如果孩子数量为 0,直接返回 0
if (ratingsSize == 0) {
return 0;
}
// 用于存储每个孩子应得的糖果数,初始值都为 1
int* candies = (int*)malloc(ratingsSize * sizeof(int));
for (int i = 0; i < ratingsSize; i++) {
candies[i] = 1;
}
// 第一次从左到右遍历数组
// 如果当前孩子的评分比左边孩子高,则当前孩子的糖果数比左边孩子多 1
for (int i = 1; i < ratingsSize; i++) {
if (ratings[i] > ratings[i - 1]) {
candies[i] = candies[i - 1] + 1;
}
}
// 第二次从右到左遍历数组
// 如果当前孩子的评分比右边孩子高,且当前孩子的糖果数不大于右边孩子的糖果数
// 则更新当前孩子的糖果数为右边孩子的糖果数加 1
for (int i = ratingsSize - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1] && candies[i] <= candies[i + 1]) {
candies[i] = candies[i + 1] + 1;
}
}
// 计算所有孩子的糖果总数
int totalCandies = 0;
for (int i = 0; i < ratingsSize; i++) {
totalCandies += candies[i];
}
// 释放动态分配的内存
free(candies);
return totalCandies;
}
6. 时间复杂度和空间复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是孩子的数量。代码进行了三次遍历数组的操作,每次遍历的时间复杂度都是 O ( n ) O(n) O(n),因此总的时间复杂度为 O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n),主要用于存储每个孩子应得的糖果数的数组。
你可以使用以下方式调用这个函数:
int main() {
int ratings[] = {1, 2, 2};
int ratingsSize = sizeof(ratings) / sizeof(ratings[0]);
int result = candy(ratings, ratingsSize);
printf("最少需要的糖果数: %d\n", result);
return 0;
}
这段代码首先初始化每个孩子的糖果数为 1,然后通过两次遍历数组,根据相邻孩子的评分关系更新每个孩子的糖果数,最后将所有孩子的糖果数相加得到最少需要的糖果数。