生活中的二分场景
有一天阿东到图书馆借了 N
本书,出图书馆的时候,警报响了,于是保安把阿东拦下,要检查一下哪本书没有登记出借。阿东正准备把每一本书在报警器下过一下,以找出引发警报的书,但是保安露出不屑的眼神:你连二分查找都不会吗?
于是保安把书分成两堆,让第一堆过一下报警器,报警器响,这说明引起报警的书包含在里面;于是再把这堆书分成两堆,把第一堆过一下报警器,报警器又响,继续分成两堆……
最终,检测了 logN
次之后,保安成功的找到了那本引起警报的书,露出了得意和嘲讽的笑容。于是阿东背着剩下的书走了。
从此,图书馆丢了 N - 1
本书
寻找左侧边界的二分查找
题目1: 业务负载分配
现有一个服务器集群(服务器数量为 serverNum),和一批不同类型的任务(用数组 tasks 表示,下标表示任务类型,值为任务数量)。 现需要把这批任务都分配到集群的服务器上,分配规则如下: 应业务安全要求,不同类型的任务不能分配到同一台服务器上 一种类型的多个任务可以分配在多台服务器上 「负载」定义为某台服务器所分配的任务个数,无任务的服务器负载为0。 「最高负载」定义为所有服务器中负载的最大值。 请你制定分配方案,使得分配后「最高负载」的值最小,并返回该最小值。
知识点:
# 算数运算向上取整 math.ceil()
# 算数运算向下取整 math.floor()
解答:
from typing import List
import math
class Solution:
def get_need_server(self, tasks, load):
need_server_num = 0
for task in tasks:
need_server_num += math.ceil(task / load)
return need_server_num
def get_min_load(self, server_num: int, tasks: List[int]) -> int:
left = 1
right = max(tasks)
while left <= right:
mid = (left + right) // 2
need_server_num = self.get_need_server(tasks, mid)
# 负载太小,服务器不够用
if need_server_num > server_num:
left = mid + 1
# 负载太大,服务器没有用完
elif need_server_num < server_num:
right = mid - 1
# 由于要负载最小,因此要继续向左搜索
elif need_server_num = server_num:
right = mid - 1
return left
解析:
a. 为什么while循环条件中是<=而不是<?
初始条件right = max(tasks)可以取到,即搜索区间是左闭右闭区间[left, right]。循环终止条件是left = right + 1, 即[right + 1, right]这时搜索区间为空,可以正常终止。也不会漏掉搜索元素。
b. 为什么是left = mid + 1, right = mid - 1?
本题中搜索区间是左闭右闭[left, right], 当mid搜索过时我们下一步应该搜索[left, mid - 1]或者[mid + 1, right]
c. 为什么最后return left而不是right?
因为终止条件是left = right + 1, 因此写成right + 1也可以。
题目2:在排序数组中查找元素的第一个位置和最后一个位置
https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/ 给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target,返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
解答:
from typing import List
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
if target not in nums:
return [-1, -1]
s = len(nums)
left = 0
right = s - 1
# 闭区间[left, right]
while left <= right:
mid = (left + right) // 2
if nums[mid] > target:
right = mid - 1
elif nums[mid] < target:
left = mid + 1
elif nums[mid] == target:
right = mid - 1
for i in range(left, s):
if nums[i] == target:
right = i
return [left, right]