bisect
此内容尚不支持你的语言。
- 目标: 掌握二分查找的思想和变种
- 学习内容:
- 标准二分查找
- 二分查找的变种(如查找左边界、右边界), 二分查找变形问题
- 二分查找的应用: 有序数组查找, 最优解的范围搜索, 查找边界, 旋转数组查找
- 推荐题目:
python 自带的二分查找库
Section titled “python 自带的二分查找库”def bisect( a, # 给定一个升序数组 x, # 目标值 lo:int=0, hi:int | None = None ): # 返回第一个大于目标值的索引 在 a[lo:hi] 里 ...# 时间复杂度: O(logn)from bisect import bisect
a = [1,9,9,9,200,500]print(bisect(a, 3)) # 1,a[1] = 9print(bisect(a, 9)) # 4,a[4] = 200print(bisect(a, -1)) # 0,a[0] = 1print(bisect(a, 1000)) # 6,a[6]: error
a.reverse() # [500, 200, 9, 9, 9, 1]# 找到第一个小于 x 的索引a_rev_ = [-i for i in a] # [-500, -200, -9, -9, -9, -1]print(bisect(a_rev_, -3)) # 5,a_[5] = -1print(bisect(a_rev_, -9)) # 5,a_[5] = -1print(bisect(a_rev_, -1)) # 6,a_[6]: errprint(bisect(a_rev_, -1000)) # 0,a_[0] = -500输入
第一行
第二行
第三行
输出一行,
输入输出样例
Section titled “输入输出样例”11 31 3 3 3 5 7 9 11 13 15 151 3 61 2 -1数据保证,
本题输入输出量较大,请使用较快的 IO 方式。
import sysinput = lambda: sys.stdin.readline().strip()n, m = map(int, input().split())a_ls = list(map(int, input().split()))q_ls = list(map(int, input().split()))方法一: 二分查找
Section titled “方法一: 二分查找”from bisect import bisectfrom typing import Listdef m1(n:int, m:int, a_ls:List[int], q_ls:List[int]) -> List[int]: s = set(a_ls) res = [] for q in q_ls: if q in s: res.append(bisect(a_ls, q-1)+1)# + 1是因为题目的输出是从1开始编号 else: res.append(-1) return resprint(*m1(n, m, a_ls, q_ls))实战题:2563
Section titled “实战题:2563”给你一个下标从 0 开始、长度为 n 的整数数组 nums 和两个整数 lower 和 upper, 返回公平数对的数目。
如果 (i, j) 满足以下情况, 则认为它是一个公平数对:
0 <= i < j < n且lower <= nums[i] + nums[j] <= upper示例 1:
输入: nums = [0,1,7,4,4,5], lower = 3, upper = 6 输出: 6 解释: 共计 6 个公平数对: (0,3), (0,4), (0,5), (1,3), (1,4), (1,5)。 示例 2: 输入: nums = [1,7,9,2,5], lower = 11, upper = 11 输出: 1 解释: 共计 1 个公平数对: (2,3)。 提示:
1 <= nums.length <= 10^5nums.length == n-10^9 <= nums[i] <= 10^9-10^9 <= lower <= upper <= 10^9
方法一: 枚举
Section titled “方法一: 枚举”def countFairPairs(self, nums: List[int], lower: int, upper: int) -> int: res = 0 for i in range(len(nums)): for j in range(i+1, len(nums)): if lower <= nums[i] + nums[j] <= upper: res += 1 return res方法二: 二分查找
Section titled “方法二: 二分查找”- 注意到排序不影响结果(总数)
- 对
lower <= nums[i] + nums[j] <= upper进行变形,得到lower - nums[i] <= nums[j] <= upper - nums[i], - 即对于每个
i,找到nums[j]在[lower - nums[i], upper - nums[i]]之间的个数。 j的范围是[i+1, n),所以可以对nums进行排序,然后对于每个i, 在j的范围中,找到lower - nums[i]和upper - nums[i]的位置,然后计算这两个位置之间的个数。
from bisect import bisect
def countFairPairs(self, nums: List[int], lower: int, upper: int) -> int: res = 0 nums.sort() for i in range(len(nums)): L = bisect(nums, lower - nums[i]-1, i+1) R = bisect(nums, upper - nums[i], i+1)-1 res += R - L + 1 # 索引做差+1 得到之间的个数, 对于前后都是闭区间 return res自己实现二分查找
Section titled “自己实现二分查找”python3.12 给 bisect 增加了 参数 key: func, 但是 python3.8 只有4个参数: a, x, lo=0, hi=None,
因此需要学会自己实现二分查找, 以应对更多的情况
实现思路:
- 对于区间
[lo, hi), 划分为[lo, mid)和[mid, hi),mid = (lo + hi) >> 1{:py} if x < a[mid]: hi = mid{:py},else: lo = mid+1{:py}- 重复步骤1,2 直到
lo == hi
def bisect(a, x, lo=0, hi=None, key=lambda f: f): if hi is None: hi = len(a) while lo < hi: mid = (lo + hi) >> 1 if x < key(a[mid]): hi = mid # 补上未来版本的 key else: lo = mid + 1 return loleetCode 2226
Section titled “leetCode 2226”给你一个 下标从 0 开始 的整数数组 candies 。数组中的每个元素表示大小为 candies[i] 的一堆糖果。你可以将每堆糖果分成任意数量的 子堆 ,但 无法 再将两堆合并到一起。
另给你一个整数 k 。你需要将这些糖果分配给 k 个小孩,使每个小孩分到 相同 数量的糖果。每个小孩可以拿走 至多一堆 糖果,有些糖果可能会不被分配。
返回每个小孩可以拿走的 最大糖果数目 。
示例 1:
输入:
candies = [5,8,6], k = 3输出:5解释: 可以将candies[1]分成大小分别为 5 和 3 的两堆,然后把candies[2]分成大小分别为 5 和 1 的两堆。现在就有五堆大小分别为 5、5、3、5 和 1 的糖果。可以把 3 堆大小为 5 的糖果分给 3 个小孩。可以证明无法让每个小孩得到超过 5 颗糖果。 示例 2: 输入:candies = [2,5], k = 11输出:0解释: 总共有 11 个小孩,但只有 7 颗糖果,但如果要分配糖果的话,必须保证每个小孩至少能得到 1 颗糖果。因此,最后每个小孩都没有得到糖果,答案是 0 。 示例 3:
1 <= candies.length <= 10^51 <= candies[i] <= 10^71 <= k <= 10^12
题解:
# 可知问题解一定在 [0, max(candies)] 之间# 答案就是寻找有多个 能被 res 整除的数 在[0, max(candies)] 之间# def bisect(x,lo=0,hi=None,key=lambda f: f):# # if hi is None: hi = len(a)# while lo < hi:# mid = (lo + hi) >> 1# if x > key(mid): hi = mid# else: lo = mid + 1# return lo
class Solution: def maximumCandies(self, candies: List[int], k: int) -> int: tot = sum(candies) r = tot//k if r==1: return 1 elif tot<k: return 0 lo = 1 hi = 10**12+10 key = lambda x: sum([i//x for i in candies]) while lo < hi: mid = (lo + hi) >> 1 if k > key(mid): hi = mid else: lo = mid + 1 return lo - 1 # res = bisect(k, lo=1, hi=max(candies)+1, key=) # return res -1lanqiao 3.冶炼金属
Section titled “lanqiao 3.冶炼金属”小蓝有一个神奇的炉子用于将普通金属 O 冶炼成为一种特殊金属 X。这个炉子有一个称作转换率的属性 V,V 是一个正整数,这意味着消耗 V 个普通金属 O 恰好可以冶炼出一个特殊金属 X,当普通金属 O 的数目不足 V 时,无法继续冶炼。
现在给出了 N 条冶炼记录,每条记录中包含两个整数 A 和 B,这表示本次投入了 A 个普通金属 O,最终冶炼出了 B 个特殊金属 X。每条记录都是独立的,这意味着上一次没消耗完的普通金属 O 不会累加到下一次的冶炼当中。
根据这 N 条冶炼记录,请你推测出转换率 V 的最小值和最大值分别可能是多少,题目保证评测数据不存在无解的情况。
排序数组中查找元素的first和last位置
Section titled “排序数组中查找元素的first和last位置”LeetCode 34. 在排序数组中查找元素的第一个和最后一个位置
给你一个按照非递减顺序排列的整数数组 nums, 和一个目标值 target. 请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target, 返回 [-1, -1]
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
示例 1:
输入:
nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:
nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:
nums = [], target = 0
输出:[-1,-1]
提示:
0 <= nums.length <= 10^5-10^9 <= nums[i] <= 10^9nums是一个非递减数组-10^9 <= target <= 10^9
def bisect(a, x, lo=0, hi=None): if hi is None: hi = len(a) while lo < hi: mid = (lo + hi) >> 1 if x < a[mid]: hi = mid else: lo = mid + 1 return lodef searchRange(self, nums: List[int], target: int) -> List[int]: if not nums: return [-1, -1] l = bisect(nums, target-1) r = bisect(nums, target) if l == r: return [-1, -1] return [l, r-1]搜索插入位置
Section titled “搜索插入位置”给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入:
nums = [1,3,5,6], target = 5
输出:2
示例 2:
输入:
nums = [1,3,5,6], target = 2
输出:1
示例 3:
输入:
nums = [1,3,5,6], target = 7
输出:4
提示:
1 <= nums.length <= 10^4-10^4 <= nums[i] <= 10^4nums为 无重复元素 的 升序 排列数组-10^4 <= target <= 10^4
from bisect import bisectdef searchInsert(self, nums: List[int], target: int) -> int: right = bisect(nums, target) if target == nums[right-1]: return right - 1 else: return right给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target, 如果目标值存在返回下标,否则返回 -1
示例 1:
输入:
nums = [-1,0,3,5,9,12], target = 9
输出:4解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入:
nums = [-1,0,3,5,9,12], target = 2
输出:-1解释: 2 不存在 nums 中因此返回 -1
提示:
- 你可以假设
nums中的所有元素是不重复的 n将在[1, 10000]之间nums的每个元素都将在[-9999, 9999]之间