二分查找的python实现(及相关题目)

1. 二分查找相关知识

1.1 算法介绍:

二分查找算法(Binary Search Algorithm):也叫做折半查找算法、对数查找算法。是一种在有序数组中查找某一特定元素的搜索算法。

注意, 有序数组中的一种搜索算法,有序数组中的一种搜索算法,有序数组中的一种搜索算法。当你看到有序数组时,就可以往二分查找这里想一想,看看能否运用。

基本思想:先确定带查找元素所在的区间范围,再逐步缩小范围,直到找到元素或找不到元素为止。

1.2 基本步骤:

1. 每次查找从数组的中间元素开始,如果中间的元素刚好是要查找的元素,则搜索过程结束;
2. 比较中间元素与目标元素的大小,如果中间元素大,那么搜寻左边;如果中间元素小,那么搜寻右边;**从小区间的中值查找**
3. 如果某一步骤数组为空,则代表找不到

1.3 算法思想:减而治之

减:减少问题的规模
治:解决问题
结合就是**排除法解决问题**
每一次查找,排除掉不可能存在目标元素的区间,在剩下可能存在目标元素
的区间中继续查找。

1.4 二分查找的细节问题

1. 区间的开闭问题:区间应该是左闭右开还是左闭右闭?

左闭右开:即right = len(nums), 区间内的最后一个元素取不到 
左闭右闭:即right = len(nums) - 1,区间内的最后一个元素可以取到
两种方式都有各自对应的代码,但相对来说,左闭右开的方式考虑的情况更加复杂,建议全部使用左闭右闭的形式

2. mid的取值问题:mid = (left + right) // 2,还是mid = (left + right + 1) // 2

mid = (left + right) // 2:常见写法
mid = left + (right - left) // 2:防止整形溢出写法
上述的两个写法如果为偶数个数据,取得都是靠左的数据(向下取整),其实,靠右的数据也是可以取的
mid = (left + right + 1) // 2
mid = left + (right - left + 1) // 2
原因:二分查找是通过中间选择的数值来确定下次查找的区间,并不强制要求在中间
位置,靠左靠右都可以,只是在中间的效率更高

3. 出界条件的判断:left <= right还是left < right

left <= right:出循环时,left = right + 1, 此时查找区间为[right+1, right],此时区间为空,返回-1
left < right:出循环时, left = right,此时查找区间为[right, right],
此时区间不为空,直接返回-1是错误的,还需要加一个判断条件

4. 搜索区间范围的选择:left = mid + 1、right = mid - 1、left = mid、 right = mid怎么写

这是我们需要知道,二分查找算法的两个思路:直接找与排除法

  1. 直接找:在循环体中找到元素直接返回结果
    思路:查看中心位置的元素nums[mid]
    1. 与目标元素相等,直接返回下表
    2. 小于目标元素,将左节点设置为mid + 1, 再在[mid+1, right]区间内寻找
    3. 大于目标元素,将右节点设置为mid - 1, 再在[left, mid - 1]区间内寻找
    退出循环时,区间内不一定不存在目标元素
    适用范围:要查找的元素性质简单,数组中都是非重复元素
  2. 排除法:在循环体中排除目标元素一定不存在的区间
    思路:
    1. 取中间位置mid,根据判断条件先将目标元素一定不存在的区间排除
    2. 再在剩余区间继续查找元素,继续根据条件排除不存在的区间
    3. 直到区间中只剩下最后一个元素,然后再判断这个元素是否是目标元素
    适用范围:解决复杂题目,比如查找一个数组里可能不存在的元素,找边界问题等

1.5 两种思路的代码实现:

1. 直接找的代码实现

class Solution:
	def search(self, nums:List[int], target:int):
		left = 0
		right = len(nums) - 1
		
		while left <= right:
			mid = (left + right) // 2
			if nums[mid] == target:
				return mid
			elif nums[mid] > target:
				right = mid - 1
			else:
				left = mid + 1
		return -1

2. 排除法的代码实现

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

2. 二分查找的相关题目

35.搜索查找位置

  1. 思路:一般二分查找的思路,只不过在判断出来mid与target的大小关系后,可以顺便查看一下与相邻元素的大小关系从而决定是否返回结果
class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        left = 0
        right = len(nums) - 1
        if target > nums[-1]:
            return len(nums)
        if target < nums[0]:
            return 0
        while left <= right:
            mid = (right + left) // 2
            if nums[mid] == target:
                return mid
            elif nums[mid] > target:
                if nums[mid - 1] < target:
                    return mid
                right = mid - 1
            else:
                if nums[mid + 1] > target:
                    return mid + 1
                left = mid + 1

374.猜数字大小

  1. 思路:一般二分查找的思路,只不过在判断mid与target的大小关系时使用了他给定的guess函数来表明大小关系。
  2. 代码实现
class Solution:
    def guessNumber(self, n: int) -> int:
        left = 0
        right = n
        while left <= right:
            mid = (left + right) // 2
            if guess(mid) == 0:
                return mid
            elif guess(mid) == 1:
                left = mid + 1
            else:
                right = mid - 1

69.Sqrt(x)

  1. 思路:使用折半查找,跟上文中的搜索查找位置有异曲同工之妙
  2. 代码实现:
class Solution:
    def mySqrt(self, x: int) -> int:
        left, right = 0, x
        while left <= right:
            mid = (left + right) // 2
            if mid * mid == x:
                return mid
            elif mid * mid < x:
                if (mid + 1) ** 2 > x:
                    return mid
                left = mid + 1
            else:
                if (mid - 1) ** 2 < x:
                    return mid - 1
                right = mid - 1

167.两数之和||-输入有序数组

  1. 思路:
    1. 先确定一个数字,再使用二分查找来寻找另外一个数字
      注意的点:
      1. 二分查找时记得同一个数字不能使用两次
    2. 双指针法
  2. 代码实现
    1. 二分查找
    class Solution:
        def twoSum(self, numbers: List[int], target: int) -> List[int]:
            n = len(numbers)
            for i in range(n):
                res = target - numbers[i]
                left = i
                right = n - 1
                while left <= right:
                    mid = (left + right) // 2
                    if numbers[mid] == res:
                        if i == mid and numbers[mid + 1] == numbers[mid]:
                            return [i + 1, mid + 2]
                        return [i + 1, mid + 1]
                    elif numbers[mid] > res:
                        right = mid - 1
                    else:
                        left = mid + 1
    
    1. 双指针
    class Solution:
        def twoSum(self, numbers: List[int], target: int) -> List[int]:
            left = 0
            right = len(numbers) - 1
            while left < right:
                if numbers[left] + numbers[right] == target:
                    return [left + 1, right + 1]
                elif numbers[left] + numbers[right] > target:
                    right -= 1
                else:
                    left += 1
            
    

1011.在D天内送达包裹的能力

  1. 思路:使用二分查找时,我们需要先设定我们查找元素的左边界以及右边界。在这道题中,左边界即为weights中的最大值(不然最大值没办法运输),右边界即为weights中元素之和(一天之内能运送完所有元素)。
    同时,我们还需要对其进行判定(按照对应元素下所需天数的变焦),如果所需天数小于目标天数,则排除掉mid右边的元素;否则,排除掉目标左边的元素。
  2. 代码实现:
class Solution:
    def shipWithinDays(self, weights: List[int], days: int) -> int:
        left = max(weights)
        right = sum(weights)
        while left <= right:
            mid = (left + right) // 2
            need = 1 # 表示需要的天数
            cur = 0 # 表示当天运送物资数
            for weight in weights:
                if cur + weight > mid:
                    need += 1
                    cur = 0
                cur += weight 
            if need <= days:
                right = mid - 1
            else:
                left = mid + 1
        return left

278.第一个错误的版本

  1. 思路:当一个版本为正确版本,则该版本之前的所有版本均为正确版本;当一个版本为错误版本,则该版本之后的所有版本均为错误版本。当我们将范围缩小到只剩一个元素时,即为结果
  2. 代码实现:
class Solution:
    def firstBadVersion(self, n):
        left = 1
        right = n
        while left <= right:
            mid = (left + right) // 2
            if isBadVersion(mid) == True:
                right = mid - 1
            else:
                left = mid + 1
        return left

33.搜索旋转排序数组

  1. 思路:原本想先使用二分查找找到最大值的位置,将左右两边分为两个有序数组,再使用二分查找在对应区间内区寻找目标元素,可是实现的时候有些许bug哈哈,so还是按照官方题解的思路先写一下,以后会补充第一种思路
  2. 代码实现:
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left = 0
        right = len(nums) - 1
        while left <= right:
            mid = (left + right) // 2
            if nums[mid] == target:
                return mid
            if nums[0] <= nums[mid]:
                if nums[0] <= target < nums[mid]:
                    right = mid - 1
                else:
                    left = mid + 1
            else:
                if nums[mid] < target <= nums[-1]:
                    left = mid + 1
                else:
                    right = mid - 1
        return -1

153.寻找旋转排序数组中的最小值

  1. 思路:寻找最小值的下标
    第一种情况:在这里插入图片描述
    第二种情况:
    在这里插入图片描述
    注意:等号的使用情况以及左右指针的变化情况

  2. 代码实现:

class Solution:
    def findMin(self, nums: List[int]) -> int:
        left = 0
        right = len(nums) - 1
        while left < right:
            mid = (left + right) // 2
            if nums[mid] < nums[right]:
                right = mid 
            else:
                left = mid + 1
        return nums[left]
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值