Python leetcode之使用二分查找最大值最小化/最小值最大化

该类题型一般采用二分法、回溯或两者组合求解的方法。
使用二分查找最大值最小化

划分为K个相等的子集

在这里插入图片描述
题解

回溯求解

class Solution(object):
    def canPartitionKSubsets(self, nums, k):
        nums.sort()
        target, v = sum(nums) // k, sum(nums) % k
        if v or nums[-1] > target: return False
        while nums and nums[-1] == target:
            k -= 1
            nums.pop()
        return self.backtrack(nums, [0] * k, target)
    def backtrack(self, nums, groups, target):
        if not nums: return True
        num = nums.pop()
        for i in range(len(groups)):
            if groups[i] + num <= target:
                groups[i] += num
                if self.backtrack(nums, groups, target): return True
                groups[i] -= num
            if not groups[i]: break
        nums.append(num)
        return False

爱吃香蕉的珂珂

在这里插入图片描述
在这里插入图片描述

题解

二分法

class Solution:
    import math
    def minEatingSpeed(self, piles: List[int], h: int) -> int:
        start, end = 1, max(piles) # 注意这里start必须从1开始,不能从min(piles)开始!!!
        while start <= end:
            mid = start + end >> 1
            if self.bisearch(piles, mid) > h:
                start = mid + 1
            else:
                end = mid - 1
        return start
    def bisearch(self, piles, k):
        res = 0
        for i in range(len(piles)):
            res += (piles[i] + k - 1) // k # 两数相除向上取整:(a + b - 1) // b
        return res

在D天内送达包裹的能力

在这里插入图片描述

class Solution:
    def shipWithinDays(self, weights: List[int], D: int) -> int:
        start, end = max(weights), sum(weights)
        while start <= end:
            mid = (start + end) >> 1
            if self.countday(weights, mid) > D:
                start = mid + 1
            else:
                end = mid - 1
        return start
    def countday(self, weights, target):
        day = 1
        current_w = 0
        for w in weights:
            current_w += w
            if current_w > target:
                day += 1
                current_w = w
        return day

制作m束花所需的最少天数

在这里插入图片描述

class Solution:
    def minDays(self, bloomDay: List[int], m: int, k: int) -> int:
        if m * k > len(bloomDay): return -1
        start, end = min(bloomDay), max(bloomDay)
        while start <= end:
            mid = (start + end) >> 1
            if self.funer(bloomDay, k, mid) < m:
                start = mid + 1
            else:
                end = mid - 1
        return start
    def funer(self, bloomDay, k, day):
        res = n = 0
        for i in range(len(bloomDay)):
            if bloomDay[i] <= day:
                n += 1
                if n == k:
                    res += 1
                    n = 0
            else: n = 0
        return res

完成所有工作的最短时间

在这里插入图片描述
采用二分+回溯的方法

class Solution:
    def minimumTimeRequired(self, jobs: List[int], k: int) -> int:
        start, end = max(jobs), sum(jobs)
        while start <= end:
            mid = (start + end) >> 1
            if self.check(jobs, mid, k):
                end = mid - 1
            else:
                start = mid + 1
        return start
    def funer(self, nums, groups, limit):
        if not nums: return True
        num = nums.pop()
        for i in range(len(groups)):
            if groups[i] + num <= limit:
                groups[i] += num
                if self.funer(nums, groups, limit): return True
                groups[i] -= num
                if not groups[i]: break
        nums.append(num)
        return False
    def check(self, jobs, limit, k):
        nums = sorted(jobs) # 注意:此处必须用sorted函数,而不能使用sort()!!!需要复制一份,因为后面nums要改变
        groups = [0] * k
        return self.funer(nums, groups, limit)

分割数组的最大值

在这里插入图片描述

class Solution:
    def splitArray(self, nums: List[int], m: int) -> int:
        n = len(nums)
        if n == 1: return nums[0]
        # 注意这里的start起始值为max为不是min(会出错,如[1,1,4],m = 2)
        # 因为是最小化最大值,则最大值最小都必须为nums中最大的那个数!!!
        start, end = max(nums), sum(nums)
        while start <= end:
            mid = (start + end) >> 1
            if self.binsearch(nums, mid) > m:
                start = mid + 1
            else:
                end = mid - 1
        return start
    def binsearch(self, nums, n):
        res = counter = 0
        for i in range(len(nums)):
            res += nums[i]
            if res > n:
                counter += 1
                res = nums[i]
        return counter + 1

该题与华为21年秋招的机试第三题类似,题目:
最优子序列:现有一个序列长度为m,我们要将其分解成k个子序列,1<=k<=m<=500,序列由整数组成,整数小于10^7。要求这k个子序列和的最小值尽可能大。在有多解的情况下,优先s(1)序列最大,如果s(1)相同,则s(i+1)依次往下排(这一说明,就要求下面的代码需要从后往前遍历)。

def make_parts(a,k,x):
    parts=[]
    num_part=0
    i=len(a)-1
    #从后往前遍历,目的是保证前面的子序列和能更大
    while i >= 0:
        tmp_sum = 0
        part = []
        j = i
        #划分子区域
        while j >= 0 and tmp_sum < x:
            part.append(a[j])
            tmp_sum += a[j]
            j -= 1
        i = j
        #x过大,直接返回0
        if tmp_sum < x:
            return None
        num_part += 1
        #如果x过小,那么划分完后会有多余的区域没有并入,将剩余区域并入s(1)序列
        if num_part == k:
            while i >= 0:
                part.append(a[i])
                i -= 1
        parts.append(part)
    #x偏大,不能划分k个区域
    if num_part < k:
        return None
    return parts
def search(a, k):
    start, end = 0, sum(a)
    while start <= end:
        mid = (start + end) >> 1
        if make_parts(a, k, mid):
            start = mid + 1
        else:
            end = mid - 1
    # return start - 1 # 如果返回的是最小值最大化的值,需要返回start - 1,这一点与最大值最小化值不一样!
    return make_parts(a, k, start - 1)
a = [100, 200, 300, 400, 500, 600, 700, 800, 900]
k = 3
out = search(a, k)
print(out) # out: [[900, 800], [700, 600], [500, 400, 300, 200, 100]]

这两题类似于对偶问题,第一个是求最大值最小化问题,第二个是求最小值最大化问题,但两者并不等价,编写功能函数也不一样,二分代码最后返回的值也不一样(前者只需要return start,后者需要return start - 1)!
至于为什么后者需要return start - 1本人也琢磨不透,希望懂的能够评论告诉我呀~

与之相关的最大化最小值/最小化最大值题:
建立火车站

N, K = map(int, input().split())
nums = list(map(int, input().split()))
nums.sort()
def funer(nums, k, K):
    s = 0
    for i in range(1, len(nums)):
        d = nums[i] - nums[i - 1] # 两个站点之间的距离
        # 最小化的最大值,也是最大值,如果出现了比这个值还大的,那就往里面塞车站,让它变小
        if d > k:
            s += (d - 1) // k # (d + k - 1) // k - 1 == (d - 1) // k
    return s > K 
def binsearch(nums, K):
    start, end = 1, nums[-1]
    while start <= end:
        mid = (start + end) >> 1
        if funer(nums, mid, K):
            start = mid + 1
        else:
            end = mid - 1
    return start
out = binsearch(nums, K)
print(out)

Aggressive cows

class Solution:
    def binsearch(self, nums, C):
        start, end = 1, nums[-1]
        while start <= end:
            mid = (start + end) >> 1
            if self.funer(nums, mid, C): # 说明设置的间隔小了,还可以设置更大一点
                start = mid + 1
            else:
                end = mid - 1
        return start - 1
    def funer(self, nums, m, C):
        s, counter = 0, 1 #在模拟放置的时候为了放置的尽可能的稀疏,要从第一个位置开始放
        for i in range(1, len(nums)):
            s += nums[i] - nums[i - 1]
            if s >= m: #似乎这里不取等号的话,在binsearch函数的返回中可以直接return start?反正下面leetcode的一题这两者是等价的。
                counter += 1
                s = 0
        return counter >= C
N, C = map(int, input().split())
nums = []
for _ in range(N):
    nums.append(int(input()))
nums.sort()
f = Solution()
out = f.binsearch(nums, C)
print(out)

两球之间的磁力

class Solution:
    def maxDistance(self, position: List[int], m: int) -> int:
        position.sort()
        start, end = 1, position[-1]
        while start <= end:
            mid = (start + end) >> 1
            if self.funer(position, m, mid):
                start = mid + 1
            else:
                end = mid - 1
        return start
    def funer(self, position, m, d):
        s, counter = 0, 1
        for i in range(1, len(position)):
            s += position[i] - position[i - 1]
            if s > d: # 不取等号,所以maxDistance函数的返回值是start,否则为start - 1
                counter += 1
                s = 0
        return counter >= m

小张刷题计划

class Solution:
    def minTime(self, time: List[int], m: int) -> int:
        if m >= len(time) or len(time) == 1: return 0
        start, end = 0, sum(time)
        while start <= end:
            mid = (start + end) >> 1
            if self.funer(time, m, mid):
                start = mid + 1
            else:
                end = mid - 1
        return start
    def funer(self, nums, m, mid):
        cur_sum, cur_max, counter = 0, nums[0], 1
        for i in range(1, len(nums)):
            if cur_sum + min(cur_max, nums[i]) <= mid:
                cur_sum += min(cur_max, nums[i])
                cur_max = max(cur_max, nums[i])
            else:
                counter += 1
                cur_max = nums[i]
                cur_sum = 0
        return counter > m

通过以上似乎发现,不管是求最大值最小化还是最小值最大化问题,binsearch函数的返回值都可以是:return start,当然这个前提是在funer函数中判断语句不取等号。但是发现,在求最大值最小化的题中,这两者并不等价,如果funer函数取了等号,return start,答案报错(leetcode410题可自行验证)。

对于这个问题,似乎就是二分查找里面的查找左边界/右边界的区别,可以参照一下讲解:
参考链接
通过以上分析可以得出:求最大值最小化,为二分查找寻找左侧边界;求最小值最大化,为二分查找寻找右侧边界

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值