数据结构与算法碎碎念(1)——二分查找运用

前言

今天在参加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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值