二分法模板:
1:左闭右闭区间写法
第一种写法,我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right] (这个很重要非常重要)。
区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:
while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
class Solution:def search(self, nums: List[int], target: int) -> int:left = 0right = len(nums) - 1while left <= right:mid = (left + right)//2if(target > nums[mid]):left = mid + 1if(target == nums[mid]):return midif(target < nums[mid]):right = mid - 1return -1
2:左闭右开
有如下两点:
while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
class Solution:def search(self, nums: List[int], target: int) -> int:left, right = 0, len(nums) # 定义target在左闭右开的区间里,即:[left, right)while left < right: # 因为left == right的时候,在[left, right)是无效的空间,所以使用 <middle = left + (right - left) // 2if nums[middle] > target:right = middle # target 在左区间,在[left, middle)中elif nums[middle] < target:left = middle + 1 # target 在右区间,在[middle + 1, right)中else:return middle # 数组中找到目标值,直接返回下标return -1 # 未找到目标值
下面来看二分法代码随想录中的题目:
leetcode 35
题目给出了排序数组,很大概率就是使用二分查找,但是这里需要考虑的问题就是插入位置在哪里。这里我们考虑区间是左闭右闭的情况。
class Solution:def searchInsert(self, nums: List[int], target: int) -> int:left = 0right = len(nums) - 1while left <= right:mid = (left + right) // 2if(nums[mid] == target):return midif(nums[mid]>target):right = mid - 1if(nums[mid] < target):left = mid + 1return right + 1
其余和二分查找没有区别,这里唯一需要注意的地方就是若未找到的话,返回索引可以有两种,left和right+1.这里的话可以自己找个例子写一下就可以了。
下面看这道题:
leetcode 34
你的理解基本上是正确的,尤其是关于使用两次二分查找来分别确定目标值的左边界和右边界的方法。下面我会进一步详细解释这个过程,确认你的理解,并补充一些可能的细节。
寻找右边界
当使用二分查找寻找右边界时,关键在于确保不错过最右侧的目标值。这可以通过以下步骤实现:
-
初始化:
left = 0
,right = len(nums) - 1
。 -
循环条件:
while left <= right
。 -
中间索引:
mid = (left + right) // 2
。 -
比较逻辑:
- 如果
nums[mid] == target
,则更新left = mid + 1
。这是因为我们需要确认是否有更靠右的相同目标值,所以我们将搜索范围向右推进。 - 如果
nums[mid] > target
,则更新right = mid - 1
。 - 如果
nums[mid] < target
,则更新left = mid + 1
。
- 如果
-
循环结束后:由于
left
最终指向目标值最后一次出现位置的下一个位置,所以正确的右边界是left - 1
。
寻找左边界
寻找左边界的方法与寻找右边界类似,但方向相反:
-
初始化:
left = 0
,right = len(nums) - 1
。 -
循环条件:
while left <= right
。 -
中间索引:
mid = (left + right) // 2
。 -
比较逻辑:
- 如果
nums[mid] == target
,则更新right = mid - 1
。这是因为我们需要确认是否有更靠左的相同目标值,所以我们将搜索范围向左推进。 - 如果
nums[mid] > target
,则更新right = mid - 1
。 - 如果
nums[mid] < target
,则更新left = mid + 1
。
- 如果
-
循环结束后:由于
right
最终指向目标值第一次出现位置的前一个位置,所以正确的左边界是right + 1
。
结果验证
- 目标值存在:左边界应该指向目标值的第一个位置,右边界应该指向目标值的最后一个位置。
- 目标值不存在:如果目标值不存在,左边界和右边界的差应该正好为1,因为
right
停在小于目标值的最大位置,而left
停在大于目标值的最小位置。
你的理解是正确的,而且你已经很好地掌握了二分查找在这种情况下的应用。这种方法确保了高效地找到目标值的确切位置或者确定其不存在。
具体程序如下:
class Solution:def searchRange(self, nums: List[int], target: int) -> List[int]:leftBoder = self.getleftboard(nums, target)rightBoder = self.getrightboard(nums, target)# 情况一if leftBoder == -2 or rightBoder == -2: return [-1, -1]# 情况三if rightBoder -leftBoder >1: return [leftBoder + 1, rightBoder - 1]# 情况二return [-1, -1]def getrightboard(self,nums,target):left = 0right = len(nums) - 1rightboard = -2while left <= right:mid = left + (right - left) // 2if nums[mid] > target:right = mid - 1else:left = mid + 1rightboard = leftreturn rightboarddef getleftboard(self,nums,target):left = 0right = len(nums) - 1leftboard = -2while left <= right:mid = left + (right - left) // 2if nums[mid] >= target:right = mid - 1leftboard = rightelse:left = mid + 1return leftboard
leetcode 69
在二分查找中,寻找左边界、右边界和寻找特定条件的最大或最小值虽然共享同样的基本结构,但确实存在一些关键的差异。我们可以通过比较这些方法的细节来理解这些差异。
当我们寻找左边界时,目标是找到数组中第一个等于目标值的元素的位置。对于这种情况,我们通常会在找到目标值时继续向左搜索(缩小右边界),以确保没有更早出现的相同值。
寻找右边界的目的是找到数组中最后一个等于目标值的元素的位置。在这种情况下,即使找到了目标值,我们也会继续向右搜索(增加左边界),以确保没有更晚出现的相同值。
在寻找特定条件下的最大或最小值时,如求整数 ( x ) 的平方根,我们的目标是找到最大的整数 ( k ),使得 ( k^2 \leq x )。这种情况下的二分查找与寻找右边界有点相似,因为我们在满足条件的情况下向右推进左边界,以尽可能增大 ( k ) 的值。当 ( k^2 ) 大于 ( x ) 时,我们需要减小 ( k )(缩小右边界)。
所以程序按照寻找右边界的写即可,返回left-1即可。
class Solution:def mySqrt(self, x: int) -> int:left = 1right = xif x == 0:return 0if x == 1:return 1while left <= right:mid = left +(right - left)//2if mid*mid > x:right = mid - 1else:left = mid + 1return left - 1
下面继续看一道类似的题目:
leetcode367
这道题其实我们完全可以接着上面69题的思路去做,稍加修改,因为上道题寻找的是右边界也就是刚好是第一个平方和大于num的数,所以我们再去计算下left-1的平方和是否等于num,如果等于就是有效的完全平方数,反之则不是。
具体程序如下:
class Solution:def isPerfectSquare(self, num: int) -> bool:left = 1right = numif num == 1:return Truewhile left <= right:mid = left +(right - left)//2if mid*mid > num:right = mid - 1else:left = mid + 1if right * right == num:return Trueelse:return False