今天抽时间来和大家一起捋一下二分法查找的解题思路和通用模板。笔者也从leetcode上面找了几道二分查找的题目来摸出一些类似的规律。
首先,什么是二分查找?其实质就是折半查找,先缩小一半,再缩小一半,再一步步的缩小范围,直至找出确定的目标。先看题目一:
https://leetcode.cn/problems/binary-search/
这是一道难度等级为简单级别的题目,较为基础,以这道经典的题目来展开深入;
解题思路:首先将这个列表中的元素,分为两组,左边一半,右边一半,然后将中间值与相邻的元素比较大小,
如果中间值比目标值大,那就在左边的一半里继续找;
如果中间值比目标值小,那就在右边的一半里继续找:
如果中间值等于目标值,那就直接返回下标值index
如果什么也找不到,那就在外面直接返回-1
""" 题目:给定一个n个元素有序的(升序)整型数组nums 和一个目标值target,写一个函数搜索nums中的target,如果目标值存在返回下标,否则返回 -1 输入: nums = [-1,0,3,5,9,12], target = 9 输出: 4 解释: 9 出现在 nums 中并且下标为 4 输入: nums = [-1,0,3,5,9,12], target = 2 输出: -1 解释: 2 不存在 nums 中因此返回 -1 """ def search(nums, target): left, right = 0, len(nums) - 1 # 双闭区间 while left <= right: # 由于是双闭区间,决定了当退出循环的条件是left>right的时候,如果不带等号可能会遗漏 mid = left + (right - left) // 2 # 可以防止left+right过大导致加和溢出问题 # 其实,求中间值的方法,mid = (left+right)//2 也是可以的,等同于上面那一行,但是可能会出现数据过大而造成的溢出问题 if nums[mid] == target: # 找到目标即可返回 return mid elif nums[mid] < target: # 如果中间值比目标值小,比较过后,那就可以直接在另一半里面找了,之前左侧那一半元素可以直接弃掉,此时以left=mid+1为左侧边界,重新开始找 left = mid + 1 elif nums[mid] > target: # 同理,如果中间值比目标值大,比较过后,那就可以直接在另一半里面找了,之前右侧那一半元素可以直接弃掉,此时以right=mid-1为右侧边界,重新开始找 right = mid - 1 # 如果里面的判断条件还查找不到,那就直接返回-1 return -1
我把每一步的执行过程都在稿纸尚一步一步的写出来,请看以上拍照截图
请继续看第二题:
https://leetcode.cn/problems/search-insert-position/
这道题和上一道题几乎相类似,就是多了一个插入的元素,并显示下标值
""" 二分查找插入列表中的题目 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置 输入: nums = [1,3,5,6], target = 5 输出: 2 输入: nums = [1,3,5,6], target = 2 输出: 1 输入: nums = [1,3,5,6], target = 7 输出: 4 """ def searchInsert(nums, target): left, right = 0, len(nums) - 1 # 双闭区间 while left <= right: # 由于是双闭区间,决定了当退出循环的条件是left>right的时候,如果不带等号可能会遗漏 mid = left + (right - left) // 2 # 可以防止left+right过大导致加和溢出问题 if nums[mid] == target: # 找到目标即可返回 return mid elif nums[mid] < target: left = mid + 1 elif nums[mid] > target: right = mid - 1 return right + 1
请继续看第三题:
https://leetcode.cn/problems/valid-perfect-square/ 有效的完全平方数
""" 二分查找:有效的完全平方数 给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 最好不好使用任何的内置函数sqrt等 """ def isPerfectSquare(self, num: int) -> bool: if num == 0: return True if num == 1: return True left= 0 right = num while left <= right: mid = (left + right) // 2 if mid * mid == num: return True elif mid * mid > num: right = mid - 1 else: left = mid + 1 return False
再看第四题:
https://leetcode.cn/problems/sqrtx/ x的平方数
""" 二分法: 给你一个非负整数 x ,计算并返回 x 的 算术平方根 。 由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 输入:x = 4 输出:2 输入:x = 8 输出:2 只保留整数部分 """ def mySqrt(self, x: int) -> int: left = 0 right = x while left <= right: mid = left + (right - left) // 2 # 确定命中区间 if mid * mid <= x: if (mid + 1) * (mid + 1) <= x: # 如果下一个元素的平方还小于x 则假命中 left = mid + 1 else: return mid # 否则 真命中 直接返回 else: right = mid - 1
请看最后一题:
https://leetcode.cn/problems/peak-index-in-a-mountain-array/ 山脉数组
""" 山脉数组 输入:arr = [0,2,1,0] 输出:1 输入:arr = [0,10,5,2] 输出:1 输入:arr = [3,4,5,1] 输出:2 输入:arr = [24,69,100,99,79,78,67,36,26,19] 输出:2 """ def peakIndexInMountainArray(arr): left = 1 right = len(arr) - 2 ans = 0 while left <= right: mid = left + (right - left) // 2 if arr[mid] > arr[mid + 1]: ans = mid right = mid - 1 else: left = mid + 1 return ans
所以从以上5道题中,基本可以摸出二分查找的规律了,left与right的逻辑处理尤为重要