一、搜索选择排序数组
此题为leetcode第33题
思路:和常规的二分法套路差不多,定义左右指针left和right,求得中间值mid,此时有一半数组肯定是有序的,如下图所示。如果nums[left] < nums[mid],那就是[left, mid]有序,如果nums[left] <= target < nums[mid],那么就在左半边找,否则在右半边找。如果有nums[mid] < nums[right],那就是[mid, right]有序,如果nums[mid] < target <= nums[right],那么就在右半边找,否则在左半边找。
class Solution:
def search(self, nums: List[int], target: int) -> int:
n = len(nums)
left, right = 0, n - 1
while left <= right:
# [left:mid]和[mid+1:right]至少有一个是有序的
mid = left + (right - left) // 2
if target == nums[mid]:
return mid
# 如果左半边是有序的
if nums[left] <= nums[mid]:
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
# 如果右半边是有序的
else:
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1
二、在排序数组中查找元素的第一个和最后一个位置
此题为leetcode第34题
思路:我们需要搜索两次,一次搜索目标值所在区间的左边界,另一次搜索目标值所在区间的右边界。在搜索左边界的时,找到target值后不能马上停止,要继续搜索,直到left==right。找右边界时正常搜索就可以。
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
left_index = self.search(nums, target)
if left_index == len(nums) or nums[left_index] != target:
return [-1, -1]
right_index = self.search(nums, target, False)
return [left_index, right_index - 1]
def search(self, nums, target, search_left=True):
left, right = 0, len(nums)
while left < right:
mid = left + (right - left) // 2
if target < nums[mid] or (search_left and target == nums[mid]):
right = mid
else:
left = mid + 1
return right
三、搜索插入位置
此题为leetcode第35题
思路:和基本的二分查找几乎一模一样,不同的是当target不存在时我们要找到它插入的位置,最后返回left即可
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return left
四、x的平方根
此题为leetcode第69题
思路:对于x=0或1的情况直接返回x即可。初始left=2,right=x//2,直接套用二分查找,如果不存在mid*mid==target,那么最后返回right。
class Solution:
def mySqrt(self, x: int) -> int:
if x == 0 or x == 1:
return x
left, right = 2, x // 2
while left <= right:
mid = left + (right - left) // 2
if mid * mid == x:
return mid
elif mid * mid < x:
left = mid + 1
else:
right = mid - 1
return right
五、搜索二维矩阵
此题为leetcode第74题
思路:此题的二维矩阵其实相当于一维有序数组reshape成了二维数组,我们可以还按一维数组的方式二分查找,只不过left、right和mid要转为二维矩阵的行列坐标。设矩阵形状为m行n列,初始left = 0,right = m * n - 1,mid依然是left + (right - left) // 2,那么mid对应的坐标为(mid // n, mid % n)。
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
if len(matrix) == 0 or len(matrix[0]) == 0:
return False
m, n = len(matrix), len(matrix[0])
length = m * n
left, right = 0, length - 1
while left <= right:
mid = left + (right - left) // 2
row, col = mid // n, mid % n
if matrix[row][col] == target:
return True
elif matrix[row][col] < target:
left = mid + 1
else:
right = mid - 1
return False
六、寻找峰值
此题为leetcode第162题
思路:当nums[mid] < nums[mid + 1]时,说明mid处于上升阶段,峰值肯定在mid右边,左边可抛去,left = mid + 1;当nums[mid] > nums[mid + 1]时,说明mid处于下降阶段,峰值肯定在mid左边,右边可以抛去,right = mid。那么峰值会在[left, right)区间,注意while循环条件是left < right。
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
left, right = 0, len(nums) - 1
while left < right:
mid = left + (right - left) // 2
if nums[mid] > nums[mid + 1]:
right = mid
else:
left = mid + 1
return left
七、寻找重复数
此题为leetcode第287题
思路:我们可以遍历数组,对于nums[i]我们搜索在数组中是否存在第二个这样的数,但这样的时间复杂度是O(n^2)。由于数字都在1到n之间,我们可以使用二分法查找哪个数字重复了。设left = 1,right = n,在每次循环时,我们统计nums里小于等于mid的个数count。如果mid不是重复的元素,那么count == mid,否则重复的元素肯定在[left, mid]里。
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
n = len(nums)
left, right = 1, n - 1
while left < right:
mid = left + (right - left) // 2
# 统计num <= mid的个数
count = 0
for num in nums:
if num <= mid:
count += 1
# 如果count大于mid,说明重复元素一定在[left, mid]里
if count > mid:
right = mid
else: # 否则元素在(mid, right]里
left = mid + 1
return left