135. 分发糖果
n
个孩子站成一排。给你一个整数数组 ratings
表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
-
每个孩子至少分配到
1
个糖果。 -
相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
解题思路
题目的要求是相邻两个孩子中评分更高的孩子会获得更多的糖果,这也就是说我们需要双边比较,有点像前面的求峰值点数。但是这道题让我们统计的是糖果数,双边比较的话太容易出错了。我们可以用两次单边比较求出。
第一次遍历,从左向右,这时候判断右边孩子是否大于左边孩子,如果大于,则在左边孩子获得糖果数量的基础上加1;如果不大于,则赋值为1即可。这里的初始化为赋值列表全为1即可。
第二次遍历,从右向左,这时候就要判断左孩子是否大于右孩子,如果大于,则在第一次遍历右孩子糖果数量的基础上加1,并与第一次遍历得到的左孩子糖果数量比较,取较大的那一个,因为较大的数量能同时满足两次遍历的结果;如果不大于,不作修改即可。
代码如下:
class Solution:
def candy(self, ratings: List[int]) -> int:
candy_ = [1] * len(ratings)
for i in range(1,len(ratings)):
#从左向右遍历
if ratings[i] > ratings[i - 1]:
candy_[i] = candy_[i - 1] + 1
else :
candy_[i] = 1
print(candy_)
for i in range(2,len(ratings) + 1):
#从右向左遍历
if ratings[len(ratings) - i] > ratings[len(ratings) - i + 1]:
candy_[len(ratings) - i] = max(candy_[len(ratings) - i],candy_[len(ratings) - i + 1] + 1)
print(candy_)
return sum(candy_)
860. 柠檬水找零
在柠檬水摊上,每一杯柠檬水的售价为 5
美元。顾客排队购买你的产品,(按账单 bills
支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5
美元、10
美元或 20
美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5
美元。
注意,一开始你手头没有任何零钱。
给你一个整数数组 bills
,其中 bills[i]
是第 i
位顾客付的账。如果你能给每位顾客正确找零,返回 true
,否则返回 false
。
解题思路
注意,这道题不能按照自己有多少钱来算,因为这样算的话会把10美元拆开,这样就会出错。正确的算法应该是对手里的5美元和10美元分别计数,按照其数目多少判断是否能够找零。
分情况讨论。
1.如果客户给了5美元,那么5美元的数值加1.
2.如果客户给了10美元,那么5美元的数值减1,10美元的数值加1。
3.如果客户给了20美元,那么优先花出10美元,再花出5美元。
因此,这里的贪心就是花出去尽可能多的10美元,保留5美元,因为5美元在找零中是万能的。
在以上三种情况中还存在着不同的情境,在第二种情况下,如果5美元的数量不够,那么返回False;在第三种情况下,如果10美元或5美元的数量不够,那么返回False。代码如下:
class Solution:
def lemonadeChange(self, bills: List[int]) -> bool:
five = 0
ten = 0
for i in range(len(bills)):
if bills[i] == 5:
five += 1
elif bills[i] == 10:
ten += 1
if five >= 1:
five -= 1
else:
return False
elif bills[i] == 20:
if ten >= 1 and five >= 1:
ten -= 1
five -= 1
elif ten >= 1 and five <= 0:
return False
elif ten <= 0 and five >= 3:
five -= 3
elif ten <=0 and five < 3:
return False
return True
406. 根据身高重建队列
假设有打乱顺序的一群人站成一个队列,数组 people
表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki]
表示第 i
个人的身高为 hi
,前面 正好 有 ki
个身高大于或等于 hi
的人。
请你重新构造并返回输入数组 people
所表示的队列。返回的队列应该格式化为数组 queue
,其中 queue[j] = [hj, kj]
是队列中第 j
个人的属性(queue[0]
是排在队列前面的人)。
解题思路
这道题有点像前面的糖果题,都是从二维的角度来出题的,遇到这种类型的题,我们直接单边考虑即可。
首先我们先试试按照次序k排个序,对于[[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]],得到[[5, 0], [7, 0], [6, 1], [7, 1], [5, 2], [4, 4]],这样看数组还是很乱,身高和次序都没有排好。
我们再来尝试下按身高排序,得到[[7, 0], [7, 1], [6, 1], [5, 0], [5, 2], [4, 4]],这样的话就比较清楚些了,最起码排好了大体的身高次序,接下来只需要按照次序k插入元素即可。代码如下:
class Solution:
def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
people.sort(key = lambda x: (-x[0], x[1])) #按照身高从大到小,序号从小到大排
que = []
for p in people:
que.insert(p[1],p)
return que
注意,这里insert()函数的用法是,第一项p[1]代表插入的位置,这里的p[1]即为people数组中的次序k,例如[5,0]元素,就是插入到了数组中的位置0.第二项p代表需要插入的元素。
452. 用最少数量的箭引爆气球
有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points
,其中points[i] = [xstart, xend]
表示水平直径在 xstart
和 xend
之间的气球。你不知道气球的确切 y 坐标。
一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x
处射出一支箭,若有一个气球的直径的开始和结束坐标为 x``start
,x``end
, 且满足 xstart ≤ x ≤ x``end
,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。
给你一个数组 points
,返回引爆所有气球所必须射出的 最小 弓箭数 。
解题思路
首先来画个图直观的看一下。这里的排序是为了能让最有可能重叠的球都尽量挨在一起,便于判断。
可以发现,当球的右边界大于等于下一个球的左边界时,这两个球可以被一支箭射爆,当球的右边界小于下一个球的左边界时,这个球只能单独被射爆。
再来看可以重叠射爆的情况,两个球可以重叠时,我们怎么判断下个球能不能被重叠进去呢?观察一下就可以知道,重叠的区域是有限的,我们需要更新重叠的右边界,如上图绿线。更新之后,将新边界作为上一个球的右边界,与现在的球的左边界比较即可判断。代码如下:
class Solution:
def findMinArrowShots(self, points: List[List[int]]) -> int:
points.sort(key = lambda x: (x[0], x[1]))
result = 0
for i in range(1, len(points)):
if points[i - 1][1] < points[i][0]:
result += 1
else :
points[i][1] = min(points[i - 1][1], points[i][1])
result += 1 #此处加一是因为循环中没法对最后一个球进行处理
return result
435. 无重叠区间
给定一个区间的集合 intervals
,其中 intervals[i] = [starti, endi]
。返回 需要移除区间的最小数量,使剩余区间互不重叠 。
解题思路
这道题和上道题差不多,可以按照上道题的思路进行重叠的计数,直接输出即可。代码如下:
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
result = 0 #用于标记重叠的个数
intervals.sort(key = lambda x:(x[0], x[1]))
for i in range(1, len(intervals)):
if intervals[i][0] >= intervals[i - 1][1]: #左边界大于右边界不重叠
continue
else: #重叠
result += 1
intervals[i][1] = min(intervals[i - 1][1],intervals[i][1])
return result
763. 划分字母区间
给你一个字符串 s
。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s
。
返回一个表示每个字符串片段的长度的列表。
解题思路
贪心算法的题一般看第一眼都没啥思路,所以这时候画个图,把所有必要信息列在图中可能就有思路了,如下:
这样思路就清晰多了,对于每个字符,我们统计出他的最远位置,然后与下标进行比较,如果下标与一段的最远位置相同了,就说明这是一段。代码如下:
class Solution:
def partitionLabels(self, s: str) -> List[int]:
dic = {} #哈希表统计每个字符出现的最远位置
for i in range(len(s)):
dic[s[i]] = i
ans = [] #存储答案
index_max = 0 #用于表示每一段的最大下标
len_max = 0 #用于表示每一段的最大长度
for i in range(len(s)):
index_max = max(dic.get(s[i], 0), index_max)
len_max += 1
if i == index_max:
ans.append(len_max)
len_max = 0
return ans
56. 合并区间
以数组 intervals
表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
解题思路
同样画图来看。
根据图来模拟一下过程,与前面判断重叠的方法类似,都是看下一个区间的左边界与上个区间的右边界的关系来判断,只不过这里如果判为了重叠区间,则不是在对区间的右边界取最小值了,而是取最大值,因为这并不需要多个区间都有相同的一段。只需要连接在一起即可。
因为end随重叠区间改变,所以在判断为不是重叠区间时才将不重叠区间加入到答案数组中。代码如下:
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort(key = lambda x:(x[0], x[1]))
ans = []
start = intervals[0][0] #区间数组左边界
end = intervals[0][1] #区间数组右边界
for i in range(1, len(intervals)):
#重叠
if intervals[i - 1][1] >= intervals[i][0]:
end = max(intervals[i][1], intervals[i - 1][1])
intervals[i][1] = max(intervals[i][1], intervals[i - 1][1])
#不重叠
else:
ans.append([start, end])
start = intervals[i][0] #不重叠,更新区间数组
end = intervals[i][1]
ans.append([start, end])
return ans
738. 单调递增的数字
当且仅当每个相邻位数上的数字 x
和 y
满足 x <= y
时,我们称这个整数是单调递增的。
给定一个整数 n
,返回 小于或等于 n
的最大数字,且数字呈 单调递增 。
解题思路
这个思路比较好想,但是代码有点难写。具体思路就是将数字从尾到头遍历,如果靠后的数字小于靠前的数字,那么靠后的数字就要变为9,靠前的数字就要减一,这样才满足单调递增的要求。代码如下:
class Solution:
def monotoneIncreasingDigits(self, n: int) -> int:
n_s = str(n)
index = len(n_s) #用于标记需要换为9的位置
#不是单调递增
for i in range(1, len(n_s)):
if n_s[len(n_s) - i] < n_s[len(n_s) - i - 1]:
index = len(n_s) - i
n_s = n_s[:len(n_s) - i - 1] + str(int(n_s[len(n_s) - i - 1]) - 1) + n_s[len(n_s) - i:]
for i in range(index, len(n_s)):
n_s = n_s[:i] + '9' + n_s[i + 1:]
ans = int(n_s)
return ans
关于字符串的操作还是不太熟练,还要继续练习。