Leetcode(135)——分发糖果
题目
n 个孩子站成一排。给你一个整数数组 ratings
表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
- 每个孩子至少分配到 1 个糖果。
- 相邻两个孩子评分更高的孩子会获得更多的糖果。
- 请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
示例 1:
输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。
示例 2:
输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。
提示:
- n == ratings.length
- 1 1 1 <= n <= 2 ∗ 1 0 4 2 * 10^4 2∗104
- 0 0 0 <= ratings[i] <= 2 ∗ 1 0 4 2 * 10^4 2∗104
题解
方法一:贪心算法
思路
我们可以将「相邻的孩子中,评分高的孩子必须获得更多的糖果」这句话拆分为两个规则,分别处理。
- 左规则:当
ratings
[
i
−
1
]
<
ratings
[
i
]
\textit{ratings}[i - 1] < \textit{ratings}[i]
ratings[i−1]<ratings[i] 时,
i
i
i 号学生的糖果数量将比
i
−
1
i - 1
i−1 号孩子的糖果数量多。
- 局部最优:只要右边评分比左边大,右边的孩子就多一个糖果 c a n d y V e c [ i ] = c a n d y V e c [ i − 1 ] + 1 candyVec[i] = candyVec[i - 1] + 1 candyVec[i]=candyVec[i−1]+1;
- 全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果
- 右规则:当
ratings
[
i
]
>
ratings
[
i
+
1
]
\textit{ratings}[i] > \textit{ratings}[i + 1]
ratings[i]>ratings[i+1] 时,
i
i
i 号学生的糖果数量将比
i
+
1
i + 1
i+1 号孩子的糖果数量多。
- 局部最优:取 c a n d y V e c [ i + 1 ] + 1 candyVec[i + 1] + 1 candyVec[i+1]+1 和 c a n d y V e c [ i ] candyVec[i] candyVec[i] 最大的糖果数量,保证第 i i i 个小孩的糖果数量即大于左边的也大于右边的;
- 全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。
我们遍历该数组两次,处理出每一个学生分别满足左规则或右规则时,最少需要被分得的糖果数量。每个人最终分得的糖果数量即为这两个数量的最大值。
具体地,以左规则为例:我们从左到右遍历该数组,假设当前遍历到位置 i i i,如果有 ratings [ i − 1 ] < ratings [ i ] \textit{ratings}[i - 1] < \textit{ratings}[i] ratings[i−1]<ratings[i] 那么 i i i 号学生的糖果数量将比 i - 1i−1 号孩子的糖果数量多,我们令 left [ i ] = left [ i − 1 ] + 1 \textit{left}[i] = \textit{left}[i - 1] + 1 left[i]=left[i−1]+1 即可,否则我们令 left [ i ] = 1 \textit{left}[i] = 1 left[i]=1。
在实际代码中,我们先计算出左规则 left \textit{left} left 数组,在计算右规则的时候只需要用单个变量记录当前位置的右规则,同时计算答案即可。
为什么取最大值是正确的思考:
我们取序列中的任意两点,A B
- 如果 A > B ,则按照左规则处理后,B不会比A多;按照右规则处理后,A一定比B多,那么A一定会被更新(变大),但L、R规则仍然成立:B不会比A多,A一定比B多;
- 同理可讨论 A<B;
- 当 A == B,A、B的值无论如何更新,都不影响 L、R规则
综上,取最大值后不影响某一规则的成立。
代码实现
class Solution {
public:
int candy(vector<int>& ratings) {
int kid, size = ratings.size();
vector<int> num(size, 1);
kid = 1;
while(kid < size){
if(ratings[kid-1] < ratings[kid]) num[kid] = num[kid-1] + 1;
kid++;
}
kid = size-1;
while(kid > 0){
if(ratings[kid-1] > ratings[kid]) num[kid-1] = max(num[kid-1], num[kid] + 1);
kid--;
}
return accumulate(num.begin(), num.end(), 0);
}
};
复杂度分析
时间复杂度:
O
(
n
)
O(n)
O(n),其中
n
n
n 是孩子的数量。我们需要遍历两次数组以分别计算满足左规则或右规则的最少糖果数量。
空间复杂度:
O
(
n
)
O(n)
O(n),其中
n
n
n 是孩子的数量。我们需要保存所有的左规则对应的糖果数量。
方法二:常数级空间复杂度
思路
注意到糖果总是尽量少给,且从 1 1 1 开始累计,每次要么比相邻的同学多给一个,要么重新置为 1 1 1。依据此规则,我们可以画出下图:
其中相同颜色的柱状图的高度总恰好为
1
,
2
,
3
…
1,2,3 \dots
1,2,3…。
而高度也不一定一定是升序,也可能是
…
3
,
2
,
1
\dots 3,2,1
…3,2,1 的降序:
注意到在上图中,对于第三个同学,他既可以被认为是属于绿色的升序部分,也可以被认为是属于蓝色的降序部分。因为他同时比两边的同学评分更高。我们对序列稍作修改,下面是新序列——我们注意到右边的降序部分变长了(从3变为了4),使得第三个同学不得不被分配 4 4 4 个糖果。
依据前面总结的规律,我们可以提出本题的解法:我们从左到右枚举每一个同学,记前一个同学分得的糖果数量为 pre \textit{pre} pre:
- 如果当前同学比上一个同学评分高,说明我们就在最近的递增序列中,直接分配给该同学 pre + 1 \textit{pre} + 1 pre+1 个糖果即可。
- 否则我们就在一个递减序列中,我们直接分配给当前同学一个糖果,并把该同学所在的递减序列中所有的同学都再多分配一个糖果,以保证糖果数量还是满足条件。
- 我们无需显式地额外分配糖果,只需要记录当前的递减序列长度,即可知道需要额外分配的糖果数量。
- 同时注意当当前的递减序列长度和上一个递增序列等长时,需要把最近的递增序列的最后一个同学也并进递减序列中。
这样,我们只要记录当前递减序列的长度 dec \textit{dec} dec,最近的递增序列的长度 inc \textit{inc} inc 和前一个同学分得的糖果数量 pre \textit{pre} pre 即可。
代码实现
class Solution {
public:
int candy(vector<int>& ratings) {
int n = ratings.size();
int ret = 1;
int inc = 1, dec = 0, pre = 1;
for (int i = 1; i < n; i++) {
if (ratings[i] >= ratings[i - 1]) {
dec = 0;
pre = ratings[i] == ratings[i - 1] ? 1 : pre + 1;
ret += pre;
inc = pre;
} else {
dec++;
if (dec == inc) {
dec++;
}
ret += dec;
pre = 1;
}
}
return ret;
}
};
复杂度分析
时间复杂度:
O
(
n
)
O(n)
O(n),其中
n
n
n 是孩子的数量。我们需要遍历两次数组以分别计算满足左规则或右规则的最少糖果数量。
空间复杂度:
O
(
1
)
O(1)
O(1),我们只需要常数的空间保存若干变量。