分发糖果
文章目录
- 分发糖果
-
- [1 题目描述](#1 题目描述)
- [2 题目分析](#2 题目分析)
-
- [2.1 寻找波峰波谷](#2.1 寻找波峰波谷)
- [2.2 从波底往波峰攀爬!](#2.2 从波底往波峰攀爬!)
- [2.2 计算糖果](#2.2 计算糖果)
- [3 代码](#3 代码)
- 附录1
1 题目描述
https://leetcode.cn/problems/candy/
n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
- 每个孩子至少分配到 1 个糖果。
- 相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
示例 1:
输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。
示例 2:
输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。
2 题目分析
首先利用实例分析,我们假设所有小孩子的评分为[17, 18, 86, 49, 18, 42, 39, 72, 4, 98]
:
题目让我们返回需要准备的最少糖果,最直接的想法就是:找到所有的波底分数对应的小孩,设置其糖果为1,然后朝着两边的波峰,逐步+1。
我"寻找波峰波谷"、"分发糖果"这些步骤的绘制图像的代码放在了附录1中,你可以传入你自定义的评分或者随机生成的评分,绘制出每一步的状态,然后可以在UUTool这个网站上设置gif。
2.1 寻找波峰波谷
当然,如果图像都是像上面那种评分还好,我们还有一种特殊情况:如果出现连续的相等评分怎么办?
如上图,出现了连续3个87,我们看看"题目描述"中怎么应对这种情况?示例2中,面对这种情况,是直接将第二个得87分的孩子的糖果设为1(高分不完全,等于完全不高分,太残酷了/(ㄒoㄒ)/~~),那么从实际来看,对于第二个87分这种情况,我们视为波谷。
- 如何判断波峰?假设当前索引为
i
。i==0
,则不用判断i
的左边,只考虑i
的分数是否大于等于i+1
的分数。i==len(ratings)-1
,则不用考虑i
的右边,只考虑i
的分数是否大于等于i-1
的分数。
换句话说,我们对i
是否为波峰的判断,分为i
与i-1
的rating相比以及和i+1
的rating相比。如果i==0
,不考虑左边;如果i==len(ratings)-1
,不考虑右边。
如何判断波谷,其实也是同样的方式。
python
is_t_left = (i == 0) or (ratings[i - 1] <= ratings[i])
is_t_right = (i == len(ratings) - 1) or (ratings[i] >= ratings[i + 1])
is_top = is_t_left and is_t_right
is_b_left = (i == 0) or (ratings[i - 1] >= ratings[i])
is_b_right = (i == len(ratings) - 1) or (ratings[i] <= ratings[i + 1])
is_bottom = is_b_left and is_b_right
if is_top:
tops.append(i)
if is_bottom:
bottoms.append(i)
这里有一个疑问,为什么是"大于等于",而不是"大于"?
很简单,我们看第一个87,其和右边相等,但是还是满足是一个波峰。
但是这样的判断方式有一个问题,假设ratings[i]==ratings[i-1]==ratings[i+1]
,i
既满足对于波峰的判断条件,也满足对于波谷的判断条件,也就是说,i
这个点即是波峰也是波谷。
没事,只要我们能判断出来这个i
是波谷就行,叠加一个波峰的标志对后面没有影响,看后续代码就行。
2.2 从波底往波峰攀爬!
已经到达谷底了,往哪走都是上升!!------------不是鲁迅说的
接下来,我们对所有的bottom进行遍历,先将bottom位置设为1,然后往左往右分别+1。
这里需要注意了,有些点既是top也是bottom,假设我们从i
开始向左向右,只要碰到bottom
,不管是不是top
,都要停下来。
然后,我们看上面那张图,从i=0
向右到达2
,从i==4
向左到达2
,到达top的时候都会对应一个值,这里恰好都是3,那么我再举一个例子:
这张图中,从不同的方向到达top
,一个对应2,一个对应3,我们取最大值。这样就可以满足candy[2]>candy[1]
,也满足candy[2]>candy[3]
。
python
for b in bottoms:
res[b] = 1 # 谷底设为1
if b > 0: # 可以往左走
left = b - 1
while (left >= 0) and (left not in bottoms): # left not in bottoms 注意不要碰到波峰波谷结合体
if left in tops: # 遇到波峰,先更新成最大值,再break
res[left] = max(res[left + 1] + 1, res[left])
break
else:
res[left] = res[left + 1] + 1 # 没有异常,直接+1
left = left - 1
if b < len(ratings) - 1:
right = b + 1
while (right < len(ratings)) and (right not in bottoms):
res[right] = res[right - 1] + 1 # 包括top也一起更新
if right in tops:
break # 这里为什么直接break呢,因为此时的top还没有被除了b小孩外的其他小孩到达过。
right = right + 1
2.2 计算糖果
python
candy = 0
for c in res:
candy = candy + c
3 代码
python
class Solution(object):
def candy(self, ratings):
"""
:type ratings: List[int]
:rtype: int
"""
bottoms = []
tops = []
res = [0 for _ in range(len(ratings))]
for i in range(len(ratings)):
is_b_left = (i == 0) or (ratings[i - 1] >= ratings[i])
is_b_right = (i == len(ratings) - 1) or (ratings[i] <= ratings[i + 1])
is_bottom = is_b_left and is_b_right
if is_bottom:
bottoms.append(i)
is_t_left = (i == 0) or (ratings[i - 1] <= ratings[i])
is_t_right = (i == len(ratings) - 1) or (ratings[i] >= ratings[i + 1])
is_top = is_t_left and is_t_right
if is_top:
tops.append(i)
for b in bottoms:
res[b] = 1
if b > 0:
left = b - 1
while (left >= 0) and (left not in bottoms):
if left in tops:
res[left] = max(res[left + 1] + 1, res[left])
break
else:
res[left] = res[left + 1] + 1
left = left - 1
if b < len(ratings) - 1:
right = b + 1
while (right < len(ratings)) and (right not in bottoms):
res[right] = res[right - 1] + 1
if right in tops:
break
right = right + 1
candy = 0
for c in res:
candy = candy + c
return candy
此时我们注意到,(left not in bottoms)
和(right not in bottoms)
可能会增加耗时,那么我考虑可以增加一个set来代替遍历查询
python
# 将bottoms变成set,方便查找
bottoms_set = set(bottoms)
(left not in bottoms_set)
(right not in bottoms_set)
即
python
class Solution(object):
def candy(self, ratings):
"""
:type ratings: List[int]
:rtype: int
"""
bottoms = []
tops = []
res = [0 for _ in range(len(ratings))]
for i in range(len(ratings)):
is_b_left = (i == 0) or (ratings[i - 1] >= ratings[i])
is_b_right = (i == len(ratings) - 1) or (ratings[i] <= ratings[i + 1])
is_bottom = is_b_left and is_b_right
if is_bottom:
bottoms.append(i)
is_t_left = (i == 0) or (ratings[i - 1] <= ratings[i])
is_t_right = (i == len(ratings) - 1) or (ratings[i] >= ratings[i + 1])
is_top = is_t_left and is_t_right
if is_top:
tops.append(i)
# 将bottoms变成set,方便查找
bottoms_set = set(bottoms)
for b in bottoms:
res[b] = 1
if b > 0:
left = b - 1
while (left >= 0) and (left not in bottoms_set):
if left in tops:
res[left] = max(res[left + 1] + 1, res[left])
break
else:
res[left] = res[left + 1] + 1
left = left - 1
if b < len(ratings) - 1:
right = b + 1
while (right < len(ratings)) and (right not in bottoms_set):
res[right] = res[right - 1] + 1
if right in tops:
break
right = right + 1
candy = 0
for c in res:
candy = candy + c
return candy
但是好像并没有什么卵用,大家可以尽情优化。
附录1
python
def candy(ratings):
"""
:type ratings: List[int]
:rtype: int
"""
bottoms = []
tops = []
bots = []
res = [0 for _ in range(len(ratings))]
for i in range(len(ratings)):
is_b_left = (i == 0) or (ratings[i - 1] >= ratings[i])
is_b_right = (i == len(ratings) - 1) or (ratings[i] <= ratings[i + 1])
is_bottom = is_b_left and is_b_right
if is_bottom:
bottoms.append(i)
bots.append(i)
is_t_left = (i == 0) or (ratings[i - 1] <= ratings[i])
is_t_right = (i == len(ratings) - 1) or (ratings[i] >= ratings[i + 1])
is_top = is_t_left and is_t_right
if is_top:
tops.append(i)
draw_pic(ratings, bottoms, tops, res)
for b in bottoms:
res[b] = 1
draw_pic(ratings, bottoms, tops, res)
if b > 0:
left = b - 1
while (left >= 0) and (left not in bots):
if left in tops:
res[left] = max(res[left + 1] + 1, res[left])
draw_pic(ratings, bottoms, tops, res)
break
else:
res[left] = res[left + 1] + 1
draw_pic(ratings, bottoms, tops, res)
left = left - 1
if b < len(ratings) - 1:
right = b + 1
while (right < len(ratings)) and (right not in bots):
res[right] = res[right - 1] + 1
draw_pic(ratings, bottoms, tops, res)
if right in tops:
break
right = right + 1
candy = 0
for c in res:
candy = candy + c
draw_pic(ratings, bottoms, tops, res)
return candy
def draw_pic(ratings, bottoms, tops, res):
import matplotlib.pyplot as plt
import numpy as np
# 绘制柱状图,ratings为红色,res为蓝色(透明度为0.5),绘制在同一个图中
plt.plot(range(len(ratings)), ratings, color='r', zorder=1)
plt.scatter(range(len(ratings)), ratings, color='r', zorder=100)
# 将bottoms标记出来
plt.scatter(bottoms, [ratings[i] for i in bottoms], color='g', zorder=100)
# 将这些点添加文字`bottom`,并且放置在点的下方
for i in bottoms:
plt.text(i, ratings[i] - 0.5, 'bottom', ha='center', va='top', fontsize=10)
# 将tops标记出来
plt.scatter(tops, [ratings[i] for i in tops], color='y', zorder=100)
# 将这些点添加文字`top`,并且放置在点的上方
for i in tops:
plt.text(i, ratings[i] + 0.5, 'top', ha='center', va='bottom', fontsize=10)
plt.bar(range(len(ratings)), res, color='b', alpha=0.5)
# 将数值绘制在柱状图上
for x, y in enumerate(res):
plt.text(x, y + 0.1, '%s' % y, ha='center', va='bottom')
# 设置 x 轴刻度及标签
plt.xticks(np.arange(len(ratings)), range(len(ratings)))
# show
plt.show()
# 随机生成ratings
import random
ratings = [random.randint(0, 100) for _ in range(10)]
# 绘制折线图
import matplotlib.pyplot as plt
import numpy as np
plt.plot(range(len(ratings)), ratings)
plt.scatter(range(len(ratings)), ratings)
# 设置 x 轴刻度及标签
plt.xticks(np.arange(len(ratings)), range(len(ratings)))
# 绘制y值
for x, y in enumerate(ratings):
plt.text(x, y + 1, '%s' % y, ha='center', va='bottom')
plt.show()
candy(ratings)