135. 分发糖果
困难
n
个孩子站成一排。给你一个整数数组 ratings
表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
- 每个孩子至少分配到
1
个糖果。 - 相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
确定一边之后,再确定另一边
局部最优:只要右边评分比左边大,右边的孩子就多一个糖果。
全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果
- 一次是从左到右遍历,只比较右边孩子评分比左边大的情况。
- 一次是从右到左遍历,只比较左边孩子评分比右边大的情况。
情况一:右比左高(从前向后遍历)
情况二:左比右高(从后向前遍历)
⚠️✅ 第二次遍历时,candy[i]的取值要将第一次遍历的结果纳入考虑,取最大值。
class Solution:
def candy(self, ratings: List[int]) -> int:
candy = [1]*len(ratings)
# 情况一:比较右比左高
for i in range(len(ratings)):
if i > 0 and ratings[i]>ratings[i-1]:
candy[i] = candy[i-1]+1
else:
candy[i] = 1
# print(candy)
# 情况二:比较左比右高
for i in range(len(ratings)-1, -1, -1):
if i < len(ratings)-1 and ratings[i] > ratings[i+1]:
candy[i] = max(candy[i], candy[i+1]+1)
# print(candy)
return sum(candy)
860. 柠檬水找零
简单
在柠檬水摊上,每一杯柠檬水的售价为 5
美元。顾客排队购买你的产品,(按账单 bills
支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5
美元、10
美元或 20
美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5
美元。
注意,一开始你手头没有任何零钱。
给你一个整数数组 bills
,其中 bills[i]
是第 i
位顾客付的账。如果你能给每位顾客正确找零,返回 true
,否则返回 false
。
三种情况:
- 情况一:账单是5,直接收下。
- 情况二:账单是10,消耗一个5,增加一个10
- 情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5(优先消耗10:美元10只能给账单20找零,而美元5可以给账单10和账单20找零,美元5更万能!)
局部最优:遇到账单20,优先消耗美元10,完成本次找零。
全局最优:完成全部账单的找零。
class Solution:
def lemonadeChange(self, bills: List[int]) -> bool:
five = 0
ten = 0
twenty = 0
for bill in bills:
# 情况一:收到5美元
if bill == 5:
five += 1
# 情况二:收到10美元
if bill == 10:
if five <= 0:
return False
ten += 1
five -= 1
# 情况三:收到20美元
if bill == 20:
# 先尝试使用10美元和5美元找零
if five > 0 and ten > 0:
five -= 1
ten -= 1
#twenty += 1
# 如果无法使用10美元找零,则尝试使用三张5美元找零
elif five >= 3:
five -= 3
#twenty += 1
else:
return False
return True
406. 根据身高重建队列
假设有打乱顺序的一群人站成一个队列,数组 people
表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki]
表示第 i
个人的身高为 hi
,前面 正好 有 ki
个身高大于或等于 hi
的人。
请你重新构造并返回输入数组 people
所表示的队列。返回的队列应该格式化为数组 queue
,其中 queue[j] = [hj, kj]
是队列中第 j
个人的属性(queue[0]
是排在队列前面的人)。
to be continued...
452. 用最少数量的箭引爆气球
重叠区间
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:
if len(points) == 0: return 0
points.sort(key = lambda x:x[0])
ans = 1
for i in range(1,len(points)):
# 如果左边界大于右边界,则需要更多的一只箭
if points[i][0] > points[i-1][1]:
ans += 1
else:
# 重叠则需要更新右边界(上一支箭能射穿的两个气球的右边界的最小值)
# 判断是否下一个气球是否也能被这支箭射穿
points[i][1] = min(points[i][1], points[i-1][1])
return ans
435. 无重叠区间
中等
给定一个区间的集合 intervals
,其中 intervals[i] = [starti, endi]
。返回 需要移除区间的最小数量,使剩余区间互不重叠 。
注意 只在一点上接触的区间是 不重叠的。例如 [1, 2]
和 [2, 3]
是不重叠的。
⚠️✅:intervals[i][1] = min(intervals[i-1][1],intervals[i][1])
更新右边界需要考虑删除一个范围时,删去哪一个:右边界比较大的那个,这样下一个区间重叠的可能性会变小。于是更新值应该为两个区间右边界的较小值。
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
intervals.sort(key = lambda x:x[0])
ans = 0
for i in range(1,len(intervals)):
if intervals[i][0] < intervals[i-1][1]:
ans+=1
intervals[i][1] = min(intervals[i-1][1],intervals[i][1])
return ans
56. 合并区间
中等
以数组 intervals
表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
result[-1][1]永远表示最大的右边界
若发现重叠区间,更新最大右边界result[-1][1] = max(result[-1][1], intervals[i][1]):新重叠区间没有被完全包含、被完全包含
没有重叠则加入当前数组(此时result[-1][1]也被更新)
class Solution:
def merge(self, intervals):
result = []
if len(intervals) == 0:
return result # 区间集合为空直接返回
intervals.sort(key=lambda x: x[0]) # 按照区间的左边界进行排序
result.append(intervals[0]) # 第一个区间可以直接放入结果集中
for i in range(1, len(intervals)):
if result[-1][1] >= intervals[i][0]: # 发现重叠区间
# 合并区间,只需要更新结果集最后一个区间的右边界,因为根据排序,左边界已经是最小的
result[-1][1] = max(result[-1][1], intervals[i][1])
else:
result.append(intervals[i]) # 区间不重叠
return result
738. 单调递增的数字
中等
当且仅当每个相邻位数上的数字 x
和 y
满足 x <= y
时,我们称这个整数是单调递增的。
给定一个整数 n
,返回 小于或等于 n
的最大数字,且数字呈 单调递增 。
例如:98,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先想让strNum[i - 1]--,然后strNum[i]给为9,这样这个整数就是89,即小于98的最大的单调递增整数。
遍历顺序:从前向后不可以利用前面遍历到的信息,只能从后往前
flag的必要性:1000,不设定flag来标记,按照逻辑变为0900,flag标记了从哪一位开始全部为9.
flag初始值为len(): 例如1234原本就符合题意,不进入处理逻辑。而若flag初始化为0,则全部为9999,不符合题意。
class Solution:
def monotoneIncreasingDigits(self, N: int) -> int:
# 将整数转换为字符串
strNum = str(N)
# flag用来标记赋值9从哪里开始
# 设置为字符串长度,为了防止第二个for循环在flag没有被赋值的情况下执行
flag = len(strNum)
# 从右往左遍历字符串
for i in range(len(strNum) - 1, 0, -1):
# 如果当前字符比前一个字符小,说明需要修改前一个字符
if strNum[i - 1] > strNum[i]:
flag = i # 更新flag的值,记录需要修改的位置
# 将前一个字符减1,以保证递增性质
strNum = strNum[:i - 1] + str(int(strNum[i - 1]) - 1) + strNum[i:]
# 将flag位置及之后的字符都修改为9,以保证最大的递增数字
for i in range(flag, len(strNum)):
strNum = strNum[:i] + '9' + strNum[i + 1:]
# 将最终的字符串转换回整数并返回
return int(strNum)
class Solution:
def monotoneIncreasingDigits(self, N: int) -> int:
# 将整数转换为字符串
strNum = list(str(N))
# 从右往左遍历字符串
for i in range(len(strNum) - 1, 0, -1):
# 如果当前字符比前一个字符小,说明需要修改前一个字符
if strNum[i - 1] > strNum[i]:
strNum[i - 1] = str(int(strNum[i - 1]) - 1) # 将前一个字符减1
# 将修改位置后面的字符都设置为9,因为修改前一个字符可能破坏了递增性质
for j in range(i, len(strNum)):
strNum[j] = '9'
# 将列表转换为字符串,并将字符串转换为整数并返回
return int(''.join(strNum))