【Leetcode-Python】-二分搜索模板汇总与相关题目

目录

模板1

模板2

leetcode版模板

更改后模板

模板3

结语


在leetcode上专门有一个学习专题来讲解二分搜索算法,详见leetcode-二分搜索在数组中搜索满足特定条件的索引或元素时,二分搜索(Binary Search)算法就应该被考虑到了。二分的本质是两段性,只要一段满足某个性质,另一段不满足某个性质,那么就可以用二分。

二分搜索通过维护left,right这两个变量得到搜索区间,mid变量将搜索区间分为两部分。通过将nums[mid]和搜索目标值target进行比较或执行特定的搜索条件来将不可能存在target的那一半搜索区间消除,在剩余的搜索区间中进行搜索。如果在数组nums中寻找满足某条件的元素且不涉及求元素索引,我们在执行二分搜索前一般需要对数组进行排序,这样能够通过比较nums[mid]和target来不断缩短搜索区间(对于一些返回满足某条件的元素索引这类问题不能执行排序,否则会把原索引打乱)。

下面介绍三种二分搜索的实现模板:

模板1

这是最基础的二分搜索模板,适合用来解决下列两类问题:

(1)寻找有序数组中是否存在某个元素值;

(2)搜索条件中只涉及到访问数组一个索引位置的元素,不涉及nums[mid]和其相邻元素的比较。

不适合处理边界寻找问题。

搜索区间为[left,right]左闭右闭区间,跳出循环后不需要任何后续的判断操作,因为在每次循环中,都会检查元素是否被找到,如果找到就返回。如果搜索到了最后,就说明元素没有被找到,直接返回-1。

def BinarySearch(nums,target):
    if(len(nums) == 0):
        return -1
    left = 0
    right = len(nums) - 1
    while(left <= right):
        mid = (left + right) // 2
        if(nums[mid] == target):
            return mid
        if(nums[mid] < target):
            left = mid + 1
        else:
            right = mid - 1
    return -1

初始化条件:left = 0,right = len(nums)-1

循环维持条件:left <= right (循环终止条件:left > right)

在左半部分数组搜索:right = mid -1

在右半部分数组搜索:left = mid + 1

模板2

这个模板是实现二分搜索的高级形式,适合处理以下两种问题:
(1)查找有序或部分有序数组中满足某个条件的区间左边界问题。

如在【leetcode-Python】- 二分搜索 - 153 Find Minimum in Rotated Sorted Array中,寻找旋转有序数组的最小值相当于寻找数组中满足x \leq nums[-1]条件的第一个x,即求区间的左边界问题。

(2)需要通过比较nums[mid]和nums[mid + 1] 来判断局部单调性。

如在【leetcode-Python】-二分搜索-162 Find Peak Element中,由right暂存可能的Peak Element,通过一步步循环收紧搜索空间,最后当left和right相等时返回right或left。

leetcode版模板

搜索区间为[left,right)左闭右开区间,在left == right条件下跳出循环,因此在跳出循环后搜索空间中只有一个元素nums[left],因此需要判断这个元素是否满足条件。

def binarySearch(nums, target):
    """
    :type nums: List[int]
    :type target: int
    :rtype: int
    """
    if len(nums) == 0:
        return -1

    left, right = 0, len(nums)
    while left < right:
        mid = (left + right) // 2
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        else:
            right = mid

    # Post-processing:
    # End Condition: left == right
    if left != len(nums) and nums[left] == target: 
        return left
    return -1

初始化条件:left = 0,right = len(nums)

循环维持条件:left  < right (循环终止条件:left == right)

在左半部分数组搜索:right = mid

在右半部分数组搜索:left = mid + 1

在一些问题中,由于在判断左右区间的条件中涉及到对nums[right]的访问(比如需要nums[mid]和nums[right]进行比较),如果right初始化为len(nums)代码会由于索引超出范围而报错。对于这种情况,这篇文章对leetcode网站上提供的模板进行了一点点修改。主要修改内容为以下两点:

(1)将right变量的初始化数值由len(nums)改为len(nums) -1,这个修改会影响跳出循环后对nums[left]的检验方式,因为不需要检验left是否等于len(nums),只需要判断nums[left]是否和target相等即可。

(2)由于(1)中的修改,在跳出while循环的后处理中,将

    if left != len(nums) and nums[left] == target:
        return left
    return -1

改为

return nums[left] == target ? left : -1

更改后模板

def BinarySearch(nums,target):
    left = 0
    right = len(nums) - 1
    while(left < right):
        mid = (left + right) // 2 #Python这样写不会溢出,如果用其他语言用mid = left + (right - left) // 2
        if (nums[mid] < target):
            left = mid + 1
        else:
            right = mid
    return nums[left] == target ? left : -1
        

但是也存在一些特定的题目,需要将right初始化值设置为len(nums)或满足搜索空间为[left,right)左闭右开区间的right值, 以避免在一些特例中出错。如【leetcode-Python】- 二分类型 - 69 Sqrt(x)问题。

此外,如果nums数组中只有一个元素,将right初始化为len(nums)-1时,程序是不会进入while循环的(left == right == 0),因此要额外检查跳出循环后程序对nums[left]的处理方式。并且对于left指针一直向右移动直到和right指针重合,即left = right = len(nums) - 1的情况,while循环跳出,此时nums[left]可被访问,需要根据题目进行对应的判断。

将right初始化改为len(nums)-1其实并不符合模板二搜索区间为[left,right)的设定,因此如果收缩搜索区间条件和nums[right]不相关,建议在leetcode提供的模板二的基础上进行编写(即初始化right为len(nums))。

模板3

搜索区间为(left,right)左开右开区间,因此以left = mid或right = mid的形式更新左右边界(因为nums[mid] 本身就不满足条件)。 在left + 1 == right条件下跳出循环,因为此时(left,right)左开右开区间为空。在后处理中对nums[left]和nums[right]都要进行检验。

def binarySearch(nums, target):
    """
    :type nums: List[int]
    :type target: int
    :rtype: int
    """
    if len(nums) == 0:
        return -1

    left, right = 0, len(nums) - 1
    while left + 1 < right:
        mid = (left + right) // 2
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid
        else:
            right = mid

    # Post-processing:
    # End Condition: left + 1 == right
    if nums[left] == target: return left
    if nums[right] == target: return right
    return -1

初始化条件:left = 0,right = len(nums)-1

循环维持条件:left + 1 <  right (循环终止条件:left + 1 ==  right)

在左半部分数组搜索:right = mid

在右半部分数组搜索:left = mid

结语

在具体问题中应用二分搜索算法时,想清楚三个问题:循环的维持条件、搜索区间收缩的条件和边界的更新方式。

Tips:在面试的手写代码环节,可以在写完代码后,举一个简单例子来验证一下代码的正确性,判断代码是否会陷入死循环(尤其分析left和right相邻时、left和right指向同一个元素时,是否能在有限的操作后跳出循环)同时验证在一些特殊情况下返回结果的准确性。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值