一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第15天,点击查看活动详情
题目(Candy)
链接:https://leetcode-cn.com/problems/candy
解决数:1029
通过率:48.8%
标签:贪心 数组
相关公司:bytedance amazon google
n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
- 每个孩子至少分配到
1个糖果。 - 相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
示例 1:
输入: ratings = [1,0,2]
输出: 5
解释: 你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。
示例 2:
输入: ratings = [1,2,2]
输出: 4
解释: 你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。
提示:
n == ratings.length1 <= n <= 2 * 1040 <= ratings[i] <= 2 * 104
思路
5种方法实现贪心算法,从布尔值的三态到平衡三进制实现更优解
一 动态规划 · 双指针
解题思路
- 数组
r每人糖果数,初始每人1个,双指针同时分别从首尾扫描- 指针
i顺序找当前 > 上个,当前 =当前和上个 + 1取大 - 指针
j倒序找当前 > 上个,当前 =当前和上个 + 1取大
- 指针
代码
var candy = function(ratings) {
let i = 0, r = new Uint16Array(ratings.length).fill(1), j = ratings.length - 1
while (i < ratings.length - 1) {
if (ratings[++i] > ratings[i - 1]) r[i] = Math.max(r[i], r[i - 1] + 1)
if (ratings[--j] > ratings[j + 1]) r[j] = Math.max(r[j], r[j + 1] + 1)
}
return r.reduce((p, v) => p + v)
};
二 线性扫描 · 数组
解题思路
- 数组
r每人糖果数,单指针i先顺序再倒序(先倒后顺也可)。以初始第0人1个为例
代码
var candy = function(ratings) {
let i = 0, r = new Uint16Array(ratings.length)
r[0] = 1
while (++i < ratings.length) // 初始每人1个时,上解法保留if,删Math.max即可
r[i] = ratings[i] > ratings[i - 1] ? r[i - 1] + 1 : 1
i--
while (i--) // r[i] = Math.max(r[i], r[i + 1] + 1) 写成判断后,并入条件
if (ratings[i] > ratings[i + 1] && r[i + 1] + 1 > r[i]) r[i] = r[i + 1] + 1
return r.reduce((p, v) => p + v)
};
三 线性扫描 · 变量
解题思路
- 来自力扣官方
- 顺序
left和倒序right都只关心自己,不用取大,专心赋值 取大在累加r合并left和right时进行,即上解法倒序循环与reduce合并
- 顺序
代码
var candy = function(ratings) {
let i = 0, left = new Uint16Array(ratings.length), right = 1, r = 0
left[0] = 1
while (++i < ratings.length) {
left[i] = ratings[i] > ratings[i - 1] ? left[i - 1] + 1 : 1
}
while (i--) {
r += left[i] > right ? left[i] : right
right = ratings[i - 1] > ratings[i] ? right + 1 : 1
}
return r
};
四 区间 · 整型
解题思路
- 扫描评分,比较前后分数
- 递增,发
n + 1糖果(最小递增区间[1,2],当前2,n最小1)- 递减 → 递增:重置
n = 1
- 递减 → 递增:重置
- 持平,发
1糖果。重置n = 1m = 0 - 递减,发
m + 1糖果(最小递减区间[2,1],当前1,m最小0)- 递增 → 递减:重置
m = 0 m<n,m++糖果+1给递减区间最大值- 新
m(递减区间最大值)与n(递增区间最大值)相邻
- 新
m=n,m+=2补1糖给拿n人,避免相邻相等n成为递减区间最大值,新m与n - 1相邻,不会再出现相邻相等
m>n,m++糖果+1给递减区间最大值- 即
m>=n,最多糖都给拿n的人(波峰)
- 即
- 递增 → 递减:重置
- 递增,发
代码
var candy = function(ratings) {
if (ratings.length === 0) return 0
let i = 0, r = 1, n = 1, m = 0, prev2
while (++i < ratings.length) {
const cur = ratings[i], prev = ratings[i - 1]
if (cur > prev) {
if (prev < prev2) n = 1
r += ++n
} else if (cur === prev) {
n = 1
m = 0
r ++
} else {
if (prev > prev2) m = 0
if (++m === n) m++
r += m
}
prev2 = prev // 上上一人,与prev比较。递减 → 递增 重置 n 递增 → 递减 重置 m
}
return r
};
五 区间 · 布尔值
解题思路
- 布尔值
f三种状态标记:持平undefined递增true递减false。代替prev2比较prev
代码
var candy = function(ratings) {
if (ratings.length === 0) return 0
let i = 0, r = 1, n = 1, m = 0, f
while (++i < ratings.length) {
const cur = ratings[i], prev = ratings[i - 1]
if (cur > prev) {
if (f === false) n = 1
f = true
r += ++n
} else if (cur === prev) {
f = void 0
n = 1
m = 0
r ++
} else {
if (f) m = 0
f = false
if (++m === n) m++
r += m
}
}
return r
};
排行
长度10000安全整型范围随机数组,每种解法求解100次,每秒操作数
拓展
SQL规范中,布尔值有三个状态,包括真,假和未知,后者在实现时,通常用null表示
部分编程语言,当变量未定义时,或不知道真假时,有默认状态,JS是undefined
利用布尔值的三态,我们可以多表示一种状态,但这会产生迷惑代码,例如
let b
if (b === true) {}
else if (b === false) {}
else {}
推荐适合布尔值的场景:一定是二值逻辑,即非真即假
MySQL中,通过指定字段默认值或直接声明tinyint(1),避免null存在
TS中,通过枚举enum定义状态,语义更明确,利于拓展
下面微调布尔值解法,用平衡三进制定义f,即true为1,false为-1,undefined为0
var candy = function(ratings) {
if (ratings.length === 0) return 0
let i = 0, r = 1, n = 1, m = 0, f = 0
while (++i < ratings.length) {
const cur = ratings[i], prev = ratings[i - 1]
if (cur > prev) {
if (f === -1) n = 1
f = 1
r += ++n
} else if (cur === prev) {
f = 0
n = 1
m = 0
r ++
} else {
if (f === 1) m = 0
f = -1
if (++m === n) m++
r += m
}
}
return r
};