美文网首页
【试一试】问题之隐式二分问题 2020-07-27(未允禁转)

【试一试】问题之隐式二分问题 2020-07-27(未允禁转)

作者: 9_SooHyun | 来源:发表于2020-07-27 12:03 被阅读0次

    1.前言

    二分查找,我们接触最多的是,给定一个完全有序的数组(最普通的二分查找)或者一个局部有序的数组(leetcode搜索旋转数组),然后从整个数组中搜索一个值。往往整个待搜索数组的全体元素显式地展现在我们眼前。但有些时候,这个“数组”给出的方式不会那么直接,而是“隐式”地给出

    2.【试一试】问题之隐式二分问题

    875. 爱吃香蕉的珂珂
    1011. 在 D 天内送达包裹的能力
    410. 分割数组的最大值
    通过上面3个经典题阐述一下,啥是【试一试】问题

    875. 爱吃香蕉的珂珂

    珂珂喜欢吃香蕉。有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫离开了,将在 H 小时后回来。
    珂珂可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。
    珂珂想在警卫回来前吃掉所有的香蕉。
    返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)
    示例 1:
    输入: piles = [3,6,7,11], H = 8
    输出: 4
    解释:第1小时吃掉3,第2、3小时吃掉6,第4、5小时吃掉7,第6、7、8小时吃掉11

    思路
    首先最直接和自然的思路是正向思维,就是我有没有一个方法,通过某种公式规则直接推导出答案,似乎比较难。那么我们换一个思路,
    ——我们先拿一个数作为假想答案,然后去试试看照这样吃需要多少小时,如果时间超了就每小时再多吃一点,否则可以尝试每小时再少吃一点,这小学生都会
    再深入一下,珂珂最少每小时得吃一根吧,最多可以吃无数根,但因为一堆香蕉不够吃的话,也得花一小时,因此最多吃max(piles)就可以了,多吃无益。那么最终的答案必然落在[1, max(piles)]里面。这个区间是我们分析出来的,不是显式给出的;然后这个区间显然是一个有序区间,有序区间搜索一个数,妥妥地标准二分查找题

    class Solution:
        def minEatingSpeed(self, piles: List[int], H: int) -> int:
            low, high = 1, max(piles)
            while low < high:
                mid = low + (high - low) // 2
                cnt = 0
                for i in range(len(piles)):
                    remain = 0
                    if piles[i] % mid != 0:
                        remain = 1
                    cnt += (piles[i] // mid + remain)
                    if cnt > H:
                        low = mid + 1
                        break
                else:
                    high = mid
            return low
    

    回顾总结

    • 1.这类题的思路是二分查找,难点在于发现一个隐式的搜索区间从而转换为二分搜索
    • 2.二分查找,本质就是一个不断试错的过程,遍历、尝试、循环,这些真真是计算机科学的基础和精华

    1011. 在 D 天内送达包裹的能力

    传送带上的包裹必须在 D 天内从一个港口运送到另一个港口。
    传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。
    返回能在 D 天内将传送带上的所有包裹送达的船的最低运载能力。

    示例 1:
    输入:weights = [1,2,3,4,5,6,7,8,9,10], D = 5
    输出:15
    解释:
    船舶最低载重 15 就能够在 5 天内送达所有包裹,如下所示:
    第 1 天:1, 2, 3, 4, 5
    第 2 天:6, 7
    第 3 天:8
    第 4 天:9
    第 5 天:10
    请注意,货物必须按照给定的顺序装运,因此使用载重能力为 14 的船舶并将包装分成 (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) 是不允许的。

    找到运载能力的上下界,形成搜索区间[max(weights), sum(weights)]

    class Solution:
        def shipWithinDays(self, weights: List[int], D: int) -> int:
            low, high = max(weights), sum(weights)
            while low < high:
                mid = low + (high-low) // 2
                day = 1
                temp_sum = 0
                for i in range(len(weights)):
                    if temp_sum + weights[i] > mid:
                        day += 1
                        temp_sum = weights[i]
                    else:
                        temp_sum += weights[i]
                if day > D:
                    low = mid + 1
                else:
                    high = mid
            return low
    

    410. 分割数组的最大值

    给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。

    注意:
    数组长度 n 满足以下条件:
    1 ≤ n ≤ 1000
    1 ≤ m ≤ min(50, n)

    示例:
    输入:
    nums = [7,2,5,10,8]
    m = 2
    输出:
    18
    解释:
    一共有四种方法将nums分割为2个子数组。
    其中最好的方式是将其分为[7,2,5] 和 [10,8],
    因为此时这两个子数组各自的和的最大值为18,在所有情况中最小

    思路
    这题实际上和吃香蕉、运货物的本质是一样的,只不过没有场景背景,晦涩了一点。H小时吃香蕉 = D天运完 = m个子数组
    那么这个题的搜索区间就是[min(nums), sum(nums)],然后通过二分查找判断mid能否把nums分成m个子数组

    class Solution:
        def splitArray(self, nums: List[int], m: int) -> int:
            def isValid(n):
                cur_sum, group_count = 0, 1
                for i in range(len(nums)):
                    if cur_sum + nums[i] <= n:
                        cur_sum += nums[i]
                    else:
                        cur_sum = nums[i]
                        group_count += 1
                return group_count <= m
            
            low = max(nums)
            high = sum(nums)
            while low < high:
                mid = low + (high - low) // 2
                if isValid(mid):
                    high = mid
                else:
                    low = mid + 1
            return low
    

    3.小结

    • 1.本质上来说,最值(最小/最大)问题都可以通过来解决。通过试来验证当前的尝试值是否满足题意,不满足再继续更新尝试值,直到试出来答案为止。这三个题的本质都是试一试的问题
    • 2.尝试值、更新值的方式有很多,而因为这里刚好形成了一个有序区间,那么这里利用二分进行尝试,可以极大降低时间复杂度。当然,我们像小学生那样,一个数一个数地从1试到无穷大,也是可以得到结果的。二分是我们试数的手段,而不是题目的本质

    相关文章

      网友评论

          本文标题:【试一试】问题之隐式二分问题 2020-07-27(未允禁转)

          本文链接:https://www.haomeiwen.com/subject/dfzalktx.html