二分查找是在“有序数组”中利用“中值”找目标的问题
- 时间复杂度:O(logN)
两种求中值方法:
- med = (left + right) // 2
- med = (left + right) >> 1 替代写法,跟上面是等价的,速度快一些
- med = left + (right - left) // 2 #应该使用这种,因为第一种存在out of range情况
- %:表示余数;//表示:地板除(向下取整),在python3中使用。python2没有使用浮点数,则都是地板除
二分查找相对简单,理解while循环和细节就好
- while left<right,则跳出循环时left=right
- while left <= right,则跳出循环时 left = right + 1
- nums[mid]&nums[left]&nums[left]三者的比较就是二分法的核心,针对不同题目变通就好
69. x 的平方根
链接:https://leetcode-cn.com/problems/sqrtx/
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
example:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842...,
由于返回类型是整数,小数部分将被舍去。
解题思路
对于开方根求法:
- sqrt = x // sqrt
- 对比medium与x//medium来判断中值medium在开方根左侧还是右侧。
- 左侧:更新left至med + 1;右侧:更新right至med - 1
- 因退出循环时,right = left-1,即right<left。而对于2.83323,我们取2而非3。所以输出right
代码
class Solution:
def mySqrt(self, x: int) -> int:
if x < 2:
return x
l = 0
r = x
while l <= r:
med = l + ( r - l ) // 2
sqrt = x // med
if med < sqrt:
l = med +1
elif med > sqrt:
r = med -1
else:
return sqrt
return r
374. 猜数字大小
链接:https://leetcode-cn.com/problems/guess-number-higher-or-lower
- 我们正在玩一个猜数字游戏。 游戏规则如下:
- 我从1到n选择一个数字。 你需要猜我选择了哪个数字。
- 每次你猜错了,我会告诉你这个数字是大了还是小了。通过调用一个预先定义好的接口 guess(int num),它会返回 3 个可能的结果(-1,1 或 0):
- -1 : 我的数字比较小
- 1 : 我的数字比较大
- 0 : 恭喜!你猜对了!
解题思路
二分查找
- 二分查找标准解法,中间调用一次guess API比大小即可
代码
# The guess API is already defined for you.
# @param num, your guess
# @return -1 if my number is lower, 1 if my number is higher, otherwise return 0
# def guess(num: int) -> int:
class Solution:
def guessNumber(self, n: int) -> int:
l,r = 1,n
while l <= r:
med = l + ( r - l ) // 2
dst = guess(med)
if dst == 1:
l = med + 1
elif dst == -1:
r = med - 1
else:
return med
744. 寻找比目标字母大的最小字母
链接:https://leetcode-cn.com/problems/find-smallest-letter-greater-than-target/
给定一个有序的字符数组 letters 和一个字符 target,要求找出 letters 中大于 target 的最小字符,如果找不到就返回第 1 个字符。
解题思路
主干二分查找法,本题有两点需要注意:
- 当中值letters[med]=target时,需要输出后一位,等价于letters[med]<target
- 输出后一位存在超过边界情况(out of range),所以需要判断是否大于边界
- 因为要找大的,最后退出循环时l是比r大的,所以输出l。
代码
class Solution:
def nextGreatestLetter(self, letters: List[str], target: str) -> str:
l = 0
r = len(letters) -1
while l <= r:
med = l + ( r - l ) // 2
if letters[med] <= target:
l = med + 1
elif letters[med] > target:
r = med - 1
if l >= len(letters):
return letters[0]
return letters[l]
540. 有序数组中的单一元素
给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。
链接:https://leetcode-cn.com/problems/single-element-in-a-sorted-array/
examples:
输入: [1,1,2,3,3,4,4,8,8]
输出: 2
解题思路
- %:表示余数;//表示:地板除(向下取整),在python3中使用。python2没有使用浮点数,则都是地板除
- 规律:只有一个单数,其余都双数,所以总数是基数。
- 去除中值与旁边相同的数,目标在剩余数组中基数侧。如果中值本身就是孤立直接输出。
- 有个坑:在elif循环里,search_odd算上了当前nums[med,所以这里找的是偶数侧。
代码
class Solution:
def singleNonDuplicate(self, nums: List[int]) -> int:
l,h = 0,len(nums)-1
while l < h:
med = l + (h - l)//2
search_odd = (med - l)%2 == 0
if nums[med] == nums[med-1]:
if search_odd:
h = med -2
else:
l = med + 1
elif nums[med] == nums[med+1]:
if search_odd:
l = med + 2
else:
h = med - 1
else:
return nums[med]
return nums[l]
278. 第一个错误的版本
假设你有 n 个版本 [1, 2, …, n],在第x版本出错导致[x:n]版本都错,找到x。可以调用isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
链接:https://leetcode-cn.com/problems/first-bad-version
example:
给定 n = 5,并且 version = 4 是第一个错误的版本。
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。
解题思路
很标准的二分查找法
代码
# The isBadVersion API is already defined for you.
# @param version, an integer
# @return a bool
# def isBadVersion(version):
class Solution(object):
def firstBadVersion(self, n):
start = 1
end = n
while start <= end:
mid = start + (end - start) // 2
if isBadVersion(mid):
end = mid -1
else:
start = mid + 1
return start
153. 寻找旋转排序数组中的最小值
链接:https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/
升序数组在某点转折,找出数组的最小值
example:
输入: [3,4,5,1,2]
输出: 1
输入: [1,2,3]
输出: 1
解题思路
- 二分查找法
- 一个小坑是输出[1,2,3]这样的,旋转点就是nums[0],直接return nums[0]
- 循环里面:出现nums[i+1]<nums[i],i+1就是转折点,nums[i+1]也就是最小值。
- nums[start]<nums[mid]:意味着[start:mid]中间都是递增的没有转折点,start=mid + 1
- nums[start]> nums[mid]:意味着[start:mid]中间有转折点,mid也可能是转折点需要被判断,所以end = mid
- 循环结束时,start=end,返回nums[end]即可
代码
class Solution(object):
def findMin(self, nums):
start,end = 0,len(nums)-1
if nums[end]>nums[0]:
return nums[0]
while start < end:
mid = start + ( end - start ) // 2
if nums[mid] > nums[start]:
start = mid + 1
else:
end = mid
if nums[mid+1]<nums[mid]:
return nums[mid+1]
if nums[mid]<nums[mid-1]:
return nums[mid]
return nums[end]
34. 在排序数组中查找元素的第一个和最后一个位置
升序排列的整数数组 nums,和一个目标值 target。找出target在nums中的开始位置和结束位置。如不存在目标值,返回 [-1, -1]。
链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array
example:
解题思路
- 二分查找法
- start:左边界,end:右边界
- 先说特例:nums本身为空,输出[-1,-1],开头排除掉
- 找左边界:
- 要找左边界,end停靠在左边界位置(nums[end]=target),利用start=mid+1来找
- 特例:如果nums[0]=target,左边界就是0,先排除掉
- while start<end:则跳出循环时start = end,且nums[start]=nums[end]=target,输出哪个都可以
- 找右边界:
- 要找右边界,end必须停靠在右边界+1位置上(nums[end]!=target),利用start=mid+1来找
- 这里不用start=mid因为mid是向下取整,会无法跳出循环;
- 如[2,3],target=2时,当start和end的位置在1时才能跳出循环,但start永远在index=0处循环
- 特例:如果nums[len(nums)-1]=target,右边界就是len(nums)-1,先排除掉
- while start<end:则跳出循环时,start = end,且nums[start-1]=nums[end-1]=target
- 要找右边界,end必须停靠在右边界+1位置上(nums[end]!=target),利用start=mid+1来找
代码
class Solution(object):
def searchRange(self, nums, target):
if not nums:
return[-1,-1]
#find left_index
start,end = 0,len(nums)-1
if nums[start] == target:
left_index = 0
else:
while start < end:
mid = start + ( end -start ) // 2
if nums[mid] >= target:
end = mid
else:
start = mid + 1
left_index = start
#find right_index
start,end = 0,len(nums)-1
if nums[end] == target:
right_index = len(nums) - 1
else:
while start < end:
mid = start + (end - start) // 2
if nums[mid] > target:
end = mid
else:
start = mid + 1
right_index = start -1
if left_index == len(nums) or nums[left_index] != target:
return [-1,-1]
return [left_index,right_index]