力扣704,二分查找
题目是这样的
二分查找的思路就是:
先确定左右两个边界,左边界是从左往右,右边界是从右往左,所以,左边界是找的比target大的第一个值,右边界最后停的点是比target小的第一个值。
他分为左闭右闭区间和左闭右开两种情况
- 左闭右闭([left, right]),此时的right是可以取到的,那么while循环的条件就是“left <= right”
- 左闭右开([left, right)),此时的right是取不到的,那么while循环的条件是"left < right"
- 由于右边开区间,那么最右边的值取不到,在while的循环体,right迭代的时候,right = middle,在定义right的时候,right = len(nums)
接着,比left小,比right大的中间的middle值就是我们要的target的位置,middle = left + (right-left)//2,为什么不是 middle = (left + right)// 2呢,此处是防止整数溢出,left+right容易超出int的范围,所以写成middle = left + (right-left)//2,"//2"是处以2,名且下取整的意思。
此时开启我们的循环,下面十个例子,nums = [1,2,3,4,7,9,10],target = 2,left = 0, right = len(nums) -1,注意,left和right是索引!当nums[middle] > target,那就往右边走,小于就往左边走。
代码如下:
# [left, right]
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1 # 定义target在左闭右闭的区间里,[left, right]
while left <= right:
middle = left + (right - left) // 2
if nums[middle] > target:
right = middle - 1 # target在左区间,所以[left, middle - 1]
elif nums[middle] < target:
left = middle + 1 # target在右区间,所以[middle + 1, right]
else:
return middle # 数组中找到目标值,直接返回下标
return -1 # 未找到目标值
# [left, right)
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) // 2
if nums[middle] > target:
right = middle # target 在左区间,在[left, middle)中
elif nums[middle] < target:
left = middle + 1 # target 在右区间,在[middle + 1, right)中
else:
return middle # 数组中找到目标值,直接返回下标
return -1 # 未找到目标值
上面是二分查找最经典的解题思路,下面是几个二分查找的变式
力扣35: 搜索插入位置 (二分查找变式)
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
- 输入: [1,3,5,6], 5
- 输出: 2
示例 2:
- 输入: [1,3,5,6], 2
- 输出: 1
示例 3:
- 输入: [1,3,5,6], 7
- 输出: 4
示例 4:
- 输入: [1,3,5,6], 0
- 输出: 0
思路就是:采用二分查找的方法,最后返回right + 1而不是-1,因为right是比target小的第一个数,在他后面插入的位置在,所以是+1
[left, right]
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
middle = (left + right) // 2
if nums[middle] < target:
left = middle + 1
elif nums[middle] > target:
right = middle - 1
else:
return middle
return right + 1
# [left, right)
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums)
while (left < right):
middle = (left + right) // 2
if nums[middle] > target:
right = middle
elif nums[middle] < target:
left = middle + 1
else:
return middle
return right
力扣34:在排序数组中查找元素的第一个和最后一个位置(二分查找变式)
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:你可以设计并实现时间复杂度为 $O(\log n)$ 的算法解决此问题吗?
示例 1:
- 输入:nums = [5,7,7,8,8,10], target = 8
- 输出:[3,4]
示例 2:
- 输入:nums = [5,7,7,8,8,10], target = 6
- 输出:[-1,-1]
示例 3:
- 输入:nums = [], target = 0
- 输出:[-1,-1]
思路:
有三种情况
target在nums范围中,其中又有两种情况,一种是target在nums里,这种情况的话就要确定左右边界,另一种就是不在,直接返回[-1, -1]
target不在nums范围中,直接返回[-1, -1]
他这个就比之前那个难一些,要确定target位置,且数组中的值不是唯一的,就要从左右两端分别通过确定左边界,和右边界来入手了,要分别定义两个函数,一个是确定左边界的,一个是定义右边界的,我说一下定义左边边界,右边界同理,首先,和之前一样,先确定left和right的取值范围,这是写在左边界函数里的,left = 0,right = len(nums) -1(讨论左右都闭的情况),先取leftboundary=-2,你可以让他等于任何值,while的条件还是不变,当nums[middle] > target时,right = middle - 1,其他情况,left = middle +1,同时更新leftboundary = left,因为此时左边界发生了变化,同理得到rightboundary,如果他们俩的值都是-2,说明nums中没有target,返回[-1, -1],如果rightboundary > rightboundary,就返回[leftboundary + 1,rightboundary - 1],其他就是[-1,-1]
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def getRightBorder(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
rightBoder = -2 # 记录一下rightBorder没有被赋值的情况
while left <= right:
middle = left + (right-left) // 2
if nums[middle] > target:
right = middle - 1
else: # 寻找右边界,nums[middle] == target的时候更新left
left = middle + 1
rightBoder = left
return rightBoder
def getLeftBorder(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
leftBoder = -2 # 记录一下leftBorder没有被赋值的情况
while left <= right:
middle = left + (right-left) // 2
if nums[middle] >= target: # 寻找左边界,nums[middle] == target的时候更新right
right = middle - 1
leftBoder = right
else:
left = middle + 1
return leftBoder
leftBoder = getLeftBorder(nums, target)
rightBoder = getRightBorder(nums, target)
# 情况一
if leftBoder == -2 or rightBoder == -2: return [-1, -1]
# 情况三
if rightBoder -leftBoder >1: return [leftBoder + 1, rightBoder - 1]
# 情况二
return [-1, -1]
还有一种滑动指针的方法,复杂度比较低,思路就是,前面用二分查找,引入一个索引,如果这个索引等于-1,说明nums中没有target,返回[-1, -1].,其他情况的话,确定两个指针left,right,让他们的初始值都为middle,往左滑,左边界不低于0,所以while(left - 1 < 0),如果nums[left-1]=target,就更新left的值,left -= 1,此时的left就是左边界,往右滑,找右边界,右边界不低于nums的长度,更新right,然后返回[left, right]
# 解法2
# 1、首先,在 nums 数组中二分查找 target;
# 2、如果二分查找失败,则 binarySearch 返回 -1,表明 nums 中没有 target。此时,searchRange 直接返回 {-1, -1};
# 3、如果二分查找成功,则 binarySearch 返回 nums 中值为 target 的一个下标。然后,通过左右滑动指针,来找到符合题意的区间
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
def binarySearch(nums:List[int], target:int) -> int:
left, right = 0, len(nums)-1
while left<=right: # 不变量:左闭右闭区间
middle = left + (right-left) // 2
if nums[middle] > target:
right = middle - 1
elif nums[middle] < target:
left = middle + 1
else:
return middle
return -1
index = binarySearch(nums, target)
if index == -1:return [-1, -1] # nums 中不存在 target,直接返回 {-1, -1}
# nums 中存在 target,则左右滑动指针,来找到符合题意的区间
left, right = index, index
# 向左滑动,找左边界
while left -1 >=0 and nums[left - 1] == target: left -=1
# 向右滑动,找右边界
while right+1 < len(nums) and nums[right + 1] == target: right +=1
return [left, right]
力扣 69 x的平方根(二分查找的变式)
思路就是,把x当成target,把[0, x]的整数当成nums,取中间的middle,看middle* middle和target的关系
class Solution:
def mySqrt(self, x: int) -> int:
left , right = 0, x
while(left <= right):
middle = left + (right - left)//2
if middle * middle > x:
right = middle -1
elif middle * middle < x:
left = middle + 1
else:
return middle
return right
力扣367.有效的完全平方数(二分查找变式)
思路同上
class Solution:
def isPerfectSquare(self, num: int) -> bool:
left, right = 0, num
while(left <= right):
middle = left + (right - left)//2
if middle * middle < num:
left = middle + 1
elif middle * middle > num:
right = middle - 1
else:
return True
return False