前言
今天在参加Leetcode第193场周赛的时候发现其第三题的解题思路十分有趣,下面分享给大家。
题目描述
难度:中等
给你一个整数数组 bloomDay,以及两个整数 m 和 k 。
现需要制作 m 束花。制作花束时,需要使用花园中相邻的 k 朵花。
花园中有 n 朵花,第 i 朵花会在 bloomDay[i] 时盛开,恰好 可以用于 一束 花中。
请你返回从花园中摘 m 束花需要等待的最少的天数。如果不能摘到 m 束花则返回 -1 。
示例 1:
输入:bloomDay = [1,10,3,10,2], m = 3, k = 1
输出:3
解释:让我们一起观察这三天的花开过程,x 表示花开,而 _ 表示花还未开。
现在需要制作 3 束花,每束只需要 1 朵。
1 天后:[x, _, _, _, _] // 只能制作 1 束花
2 天后:[x, _, _, _, x] // 只能制作 2 束花
3 天后:[x, _, x, _, x] // 可以制作 3 束花,答案为 3
示例 2:
输入:bloomDay = [1,10,3,10,2], m = 3, k = 2
输出:-1
解释:要制作 3 束花,每束需要 2 朵花,也就是一共需要 6 朵花。而花园中只有 5 朵花,无法满足制作要求,返回 -1 。
示例 3:
输入:bloomDay = [7,7,7,7,12,7,7], m = 2, k = 3
输出:12
解释:要制作 2 束花,每束需要 3 朵。
花园在 7 天后和 12 天后的情况如下:
7 天后:[x, x, x, x, _, x, x]
可以用前 3 朵盛开的花制作第一束花。但不能使用后 3 朵盛开的花,因为它们不相邻。
12 天后:[x, x, x, x, x, x, x]
显然,我们可以用不同的方式制作两束花。
示例 4:
输入:bloomDay = [1000000000,1000000000], m = 1, k = 1
输出:1000000000
解释:需要等 1000000000 天才能采到花来制作花束
示例 5:
输入:bloomDay = [1,10,2,9,3,8,4,7,5,6], m = 4, k = 2
输出:9
提示:
- bloomDay.length == n
- 1 <= n <= 10^5
- 1 <= bloomDay[i] <= 10^9
- 1 <= m <= 10^6
- 1 <= k <= n
来源:力扣(LeetCode)
链接:原题链接
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路
乍一看之下,该题目解决思路很简单:如果所需要的花朵总数大于整数数组 bloomDay,此时无法摘到 m 束花则返回 -1 。否则一定可以在某一天摘到 m 束花,而我们要求的就是从花园中摘 m 束花需要等待的最少的天数,而且还需特别注意的一点是:k 朵花必须是相邻的。
固定一个day后,判断整数数组 bloomDay是否能摘到 m 束花是比较容易实现的,我们只需要将整数数组 bloomDay按照未成熟的花进行分组,每组整除一束花所需要的花朵数量 k 即是该组所能摘到的花束数量,再将每组所能摘到的花束数量相加,即是在当前day下整数数组 bloomDay能摘到的花束数,如果总的花束数大于m则返回True,反之。
就示例三而言其过程如下:
bloomDay = [7,7,7,7,12,7,7], m = 2, k = 3
取day = 7
此时按照未成熟的花进行分组结果如下:
[7,7,7,7] [7,7]
能构成的花束数 = 4//3 + 2//3 = 1 + 0 = 1
此时 1 < m 所以无法摘到 m 束花,返回False
而上述分组其实我们可以通过一个 curr 变量来实现,从左到右遍历整数数组 bloomDay,如果当前花是成熟的,就将 curr += 1 ,一遇到未成熟的花就停下来,然后将 curr // k 就是当前可构成的花束数量,再定义一个变量 total 来统计总的花束数量 total += curr // k,然后将 curr 置 0 继续向下扫描。其代码实现如下:
def check(day):
# 定义检查是否能制作m朵花的函数
curr = 0
total = 0
for i in range(len(bloomDay)):
if(bloomDay[i] <= day):
curr += 1
else:
total += curr // k;
curr = 0;
total += curr // k
return total >= m
有了判断是否能构成 m 束花的函数,接下来就差确定最小的 day 了,最直观的方法是对于整数数组 bloomDay 中出现的每一个 day ,我们从小到大进行逐一检查,遇到最先满足的就返回当前 day ,此种方法在整数数组 bloomDay 规模很小的时候适用,但是如果规模很大的话,可想而知其执行速度是十分缓慢的,经过笔者的验证,无法通过Leetcode的全部测试用例,那么有没有一种比较高效的方法?
思考过程
实际上是有的,寻找到最小的day相当于我们在min(bloomDay)和max(bloomDay)中找到一个最小的 day 使其满足条件,那么假设最小的 day 是 5 ,实际上大于5的day都可以满足构成m束花,而小于5的day都不能满足构成m束花。
如果min(bloomDay)是1而max(bloomDay)是7,我们从1检查到7,返回最先满足的day即可,但前面我们已经讲过顺序查找的执行效率是很慢的,那么我么们还有其他查找方法吗?我们是否可以从中间位置开始查?没错!这就是二分查找!
二分查找
二分查找的思想是将数组分为左右两部分,如果待查找的数比中间分隔的数字小,那么该数字一定落在左边部分,反之,如此反复将数组分为左右两部分进行查找,直至中间分隔的数字等于我们的查找值即找到了待查数字,二分查找每次将问题的规模缩小了一半使得其执行效率提高了不少。
二分查找解题
返回该题,我们假设当前的 day 无法构成m束花,那么所找的 minday 一定大于等于day,反之,若当前的 day 可以构成m束花,那么所找的 minday 一定小于等于day。所以我们定义left=min(bloomDay),right=max(bloomDay),然后利用二分查找来寻找满足条件的 minday ,终止条件是left > right,此时返回的day若能满足构成m束花,那么一定是最小的day,否则返回-1,完整代码如下:
class Solution:
def minDays(self, bloomDay: List[int], m: int, k: int) -> int:
def check(day):
# 定义检查是否能制作m朵花的函数
curr = 0
total = 0
for i in range(len(bloomDay)):
if(bloomDay[i] <= day):
curr += 1
else:
total += curr // k;
curr = 0;
total += curr // k
return total >= m
left, right = min(bloomDay), max(bloomDay)
while left <= right:
day = (left + right) // 2
if check(day):
right = day-1
else:
left = day+1
return left if check(left) else -1
总结
该题目十分灵活,需要转个弯才能通过所有测试用例,是一道典型的二分查找在实际应用中的体现,所以说基础的算法还是需要理解通透,并且多于时间才能真正在实际工作中应用。
后记
代码参考自 LeetCode,贡献者:bestfitting