1. 二分查找
- 排序的,或者间接排序的
- 注意边界条件,因为每次循环只触发一个条件,所以 i i i或者 j j j只能移动一步,循环条件是 i ≤ j i\le j i≤j
训练计划
题目:704. 二分查找
代码模板:
class Solution:
def search(self, nums: List[int], target: int) -> int:
i, j = 0, len(nums)-1
while i <= j:
# 取靠左的,即 1 2 3 4 取2,1 2 3 取2
# 防止计算时溢出
mid = i + ((j-i) >> 1)
if nums[mid] == target:
return mid
elif nums[mid] < target:
i = mid+1
else:
j = mid-1
return -1
代码模板:
# The isBadVersion API is already defined for you.
# @param version, an integer
# @return an integer
# def isBadVersion(version):
class Solution:
def firstBadVersion(self, n):
"""
:type n: int
:rtype: int
"""
# 特殊之处在于只有2种状态,即当跳出循环时:
# 如果是i未出错,i += 1,返回i;如果j未出错,一定是上一个版本出错,也返回i
i, j = 1, n
while i <= j:
mid = i + ((j-i) >> 1)
if isBadVersion(mid):
j = mid-1
else:
i = mid+1
return i
这道题也可以使用 i < j i<j i<j的条件判断了,并且每次只有一个指针移动。
为什么会有这样的区别呢?事实上是因为二分的区间条件不同,判断数字是3种情况,最终范围长度大于0时即是有效范围;但是判断正误只有2种情况,只有范围长度为1时,才是最终的答案,这里需要好好思考,将问题泛化到各种情况下。
题目:35. 搜索插入位置
代码模板:
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
# 3种情况,对比普通二分将返回-1变为返回合适的索引
# 考虑找不到的情况,此时i=j,并且有一个变化使while条件不成立,大了j减1,小了i+1,所以返回i
# 插入位置i表示插入到i索引之前
i, j = 0, len(nums)-1
while i <= j:
mid = i + ((j-i) >> 1)
if nums[mid] == target:
return mid
elif nums[mid] < target:
i = mid+1
else:
j = mid-1
return i
相似题目
题目:34. 在排序数组中查找元素的第一个和最后一个位置(重点)
代码模板:
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
# i和j就是区间2端点
# 3种情况
i, j = 0, len(nums)-1
while i <= j:
mid = i + ((j-i) >> 1)
if nums[mid] < target:
i = mid + 1
elif nums[mid] > target:
j = mid - 1
# mid到j都等于target
elif nums[i] != target:
i += 1
# i到mid都等于target
elif nums[j] != target:
j -= 1
else:
return [i, j]
return [-1, -1]
这道题就比较有趣了,我的实现并不是完全的二分查找,但是在一定程度上使用二分查找优化了整体的效率,有时间可以学习一下官方的题解
题目:374. 猜数字大小
代码模板:
# 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:
i, j = 1, n
while i <= j:
mid = i + ((j-i) >> 1)
if guess(mid) == 0:
return mid
elif guess(mid) == -1:
j = mid-1
else:
i = mid+1
# 返回0表示pick不在范围内
return 0
题目:658. 找到 K 个最接近的元素(考察coding能力)
代码模板:
class Solution:
def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
# 二分找到恰好大于x一点点的位置,等价于之前的查找插入位置
i, j = 0, len(arr)-1
idx = -100
while i <= j:
mid = i + ((j-i) >> 1)
if arr[mid] == x:
idx = mid
break
elif arr[mid] < x:
i = mid+1
else:
j = mid-1
if idx == -100:
idx = i
# print(idx)
# 缩小区间
if idx == 0:
return arr[:k]
elif idx == len(arr):
return arr[-k:]
else:
i, j = max(idx-k, 0), min(idx+k, len(arr)-1)
while j-i >= k:
if arr[j] - x > x - arr[i]:
j -= 1
elif arr[j] - x < x - arr[i]:
i += 1
else:
j -= 1
return arr[i:j+1]