二分查找算法

leetcode二分搜索

「二分」一共有三类常见应用,分别是「整数二分」、「实数二分」以及「二分答案」,具体场景可以归为查找最小值、查找左边界、查找右边界等。
一、二分查找框架

def BinarySerach(nums: int, target:int) -> int:
	left, right = 0, ...
	while ...:
		mid = left + (right - left) // 2  # 防止边界溢出
		if nums[mid] == target:
			...
		elif nums[mid] < target:
			...
		elif  nums[mid] > target:
			...
	return ...	

这里用else if 表示出所有可能得情况方便理解。… 部分是二分查找中常会出现问题的地方。

二、基本二分——寻找一个数
通常场景是一个有序数组,具有一定单调性。目标为查找一个数,存在返回索引,若不存在则返回-1。

def BinartSearch(nums: int, target: int) ->int:
	left, right = 0, len(nums - 1)  # 注意
	while left <= right: # 注意循环条件
		mid = left + (left - right) // 2
		if nums[mid] == target:
			return mid
		elif nums[mid] < target:
			left = mid + 1  # 注意赋值情况
		else:
			right = mid - 1  # 注意
	return -1

1、while 循环条件是 <= 而不是 <
这里将搜索区间(循环不变量)定义为闭区间[left, right],所以 right 初始化 len(nums)-1,即最后一个元素的索引。
此时停止搜索条件:
(1)找到目标值,即 nums[mid] == target,
(2)while循环终止仍没找到,返回-1。此时循环停止条件为left==right+1,区间为[rigth+1, right],显然不存在,终止循环。
注意,如果搜索区间定义为[left, right),那么right初始定义为len(nums),while循环为while left< right,终止条件为 left == right,区间为[right,right),区间非空,漏掉2,不能直接返回-1,需要进行判断:

#…
while left < right:
# …
return nums[left]==target ? left : -1

2、left与right更新值问题
当搜索区间为闭区间 [left, right]时,如果mid对应的值不是target,那么我们应当查找下一个,即[left, mid-1]或者[mid+1, right]。

三、寻找左侧边界的二分搜索

def leftBound(nums: int, target: int) -> int:
	if len(nums) == 0:
		return -1
	left, right = 0, len(nums)
	while left < right:
		if nums[mid] == target:
			right = mid
		elif nums[mid] < target:
			left = mid + 1
		elif nums[mid] > target:
			right = mid  # 注意!
	return left

1、while循环问题
这里定义的搜索区间为左开右闭区间[left, right),右边界right初始化为len(nums),所以当left==right时,搜索区间为[left, right),循环终止。

2、如果nums中没有target
函数的返回值(即 left 变量的值)取值区间是闭区间 [0, len(nums)],添加两行代码就能在正确的时候 return -1:

while left < right:
    # ...
#  target 比所有数都大
if left == len(nums):
	return -1;
#类似之前算法的处理方式
return nums[left] == target ? left : -1

3、为什么是 left = mid + 1,right = mid ?
这里搜索区间为[left, right),所以当 nums[mid] 被找到之后,下一步的搜索区间应该去掉 mid 分割成两个区间,即 [left, mid) 或 [mid + 1, right)。

4、为什么该算法可以搜索左边界?
算法对于 nums[mid] == target 这种情况的处理:

if nums[mid] == target:
	right = mid

找到 target 时缩小「搜索区间」的上界 right,在区间 [left, mid) 中继续搜索,即不断向左收缩直到查找到左边界。
5、返回值问题
其实返回left或right是一样的,因为while的终止条件是left==right。

三、查找右侧边界
与寻找左侧边界类似。

def rightBound(nums: int, target: int) -> int:
    if len(nums) == 0:
    	return -1
    	
    left, right = 0, len(nums)
    while left < right:
        mid = left + (left + right) // 2;
        if nums[mid] == target:
            left = mid + 1  # 注意
        elif nums[mid] < target:
            left = mid + 1
        elif nums[mid] > target:
            right = mid
    return left - 1  # 注意

1、为什么可以找到右边界?

    if nums[mid] == target:
        left = mid + 1

当 nums[mid] == target 时不返回,而是增大「搜索区间」的下界 left,使得区间不断向右收缩直到找到右侧边界。
2. 为什么最后返回 left - 1 ?而不是right?
while循环终止条件为 left == right,所以 left 和 right 是一样的,也可以写right-1。
减一是搜索右侧边界的特殊点,关键在于条件判断:

    if nums[mid] == target:
        left = mid + 1  # mid = left - 1

由于对 left 的更新是 left = mid + 1, while 循环结束时,nums[left] 一定不等于 target 了,而 nums[left - 1]可能是target。

3、如果 nums 中不存在 target 的处理
类似搜索左侧边界,因为left == right是while循环终止,left取值范围是[0, len(nums)],所以:

while left < right:
    # ...
if left == 0:
	return -1;
return nums[left-1] == target ? (left-1) : -1

四、总结

(1)最基本的二分查找算法:

因为初始化 right = len(nums) - 1
所以决定了「搜索区间」是 [left, right]
所以决定了 while(left <= right) 同时也决定了 left = mid+1 和 right = mid-1;

因为只需找到一个 target 的索引即可
所以当 nums[mid] == target 时可以立即返回

(2)寻找左侧边界的二分查找:

因为初始化 right = len(nums)
所以决定了的「搜索区间」是 [left, right)
所以决定了 while (left < right)
同时也决定了 left = mid+1 和 right = mid;
因为需找到 target 的最左侧索引
所以当 nums[mid] == target 时不要立即返回
而要收紧右侧边界以锁定左侧边界

(3)寻找右侧边界的二分查找:

因为初始化right = len(nums)
所以决定了「搜索区间」是[left, right)
所以 while(left < right)
同时 left = mid +1 right = mid
所以当 nums[mid]==target时不立即返回
而是要收紧左侧以逼近右侧边界

如有纰漏,敬请指正~

原文:
https://www.cnblogs.com/kyoner/p/11080078.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值