力扣hot100—系列7-二分查找

二分查找(Binary Search)的核心思想是:折半查找

前提条件:数组必须是有序的(或者具有某种单调性)。

它的基本步骤是:

  1. 找到区间的中间点 mid
  2. 如果 mid 的值等于目标值,直接返回。
  3. 如果 mid 的值大于目标值,说明目标在左半部分,收缩右边界。
  4. 如果 mid 的值小于目标值,说明目标在右半部分,收缩左边界。

1. 35. 搜索插入位置 (简单)

题目大意:

给定一个排序数组和一个目标值,如果找到目标值则返回索引;如果不存在,返回它将会被按顺序插入的位置。

零基础思路:

这就是最标准的二分查找。如果最后没找到,left 指针指向的位置正好就是它该插入的地方。

代码实现:

python 复制代码
def searchInsert(nums, target):
    left, right = 0, len(nums) - 1
    while left <= right:
        mid = (left + right) // 2
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return left  # 没找到时,left就是插入位置

2. 74. 搜索二维矩阵 (中等)

题目大意:

矩阵每一行从左到右升序,每一行的第一个数比上一行最后一个数大。给你一个 target,判断在不在矩阵里。

零基础思路:

这个矩阵其实可以看作是一个拉平了的一维长数组

  • 假设矩阵是 mn 列,总共有 m * n 个元素。
  • 一维索引 i 转换回二维坐标:行 = i // n,列 = i % n
  • 然后直接套用标准二分查找。

代码实现:

python 复制代码
def searchMatrix(matrix, target):
    m, n = len(matrix), len(matrix[0])
    left, right = 0, m * n - 1
    
    while left <= right:
        mid = (left + right) // 2
        # 将一维索引转回二维
        row, col = mid // n, mid % n
        num = matrix[row][col]
        
        if num == target:
            return True
        elif num < target:
            left = mid + 1
        else:
            right = mid - 1
    return False

3. 34. 在排序数组中查找元素的第一个和最后一个位置 (中等)

题目大意:

给定一个升序数组,找出一个给定目标值的开始位置和结束位置。

零基础思路:

普通的二分查找到一个值就会停止。这里我们需要找到"边界":

  1. 写一个函数找左边界 :当 nums[mid] == target 时,不急着返回,而是让 right = mid - 1 继续往左找。
  2. 写一个函数找右边界 :当 nums[mid] == target 时,让 left = mid + 1 继续往右找。

代码实现:

python 复制代码
def searchRange(nums, target):
    def findBound(isFirst):
        left, right = 0, len(nums) - 1
        bound = -1
        while left <= right:
            mid = (left + right) // 2
            if nums[mid] == target:
                bound = mid
                if isFirst: right = mid - 1 # 找左边,锁死右边界
                else: left = mid + 1        # 找右边,锁死左边界
            elif nums[mid] < target: left = mid + 1
            else: right = mid - 1
        return bound

    return [findBound(True), findBound(False)]

4. 33. 搜索旋转排序数组 (中等)

题目大意:

一个原本升序的数组在某个点旋转了(例如 [0,1,2,4,5,6,7] 变成 [4,5,6,7,0,1,2])。找 target

零基础思路:

旋转后的数组,如果从中点切开,一定有一半是完全有序的,另一半可能是有序也可能是包含旋转点的

  • 如果 nums[left] <= nums[mid],左半边是有序的。
    • 检查 target 是否在左半边的范围内,在就去左边,不在就去右边。
  • 否则,右半边是有序的。
    • 检查 target 是否在右半边的范围内。

代码实现:

python 复制代码
def search(nums, target):
    left, right = 0, len(nums) - 1
    while left <= right:
        mid = (left + right) // 2
        if nums[mid] == target: 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

5. 153. 寻找旋转排序数组中的最小值 (中等)

题目大意:

找出旋转排序数组中的最小元素。

零基础思路:

最小值其实就是旋转点(也就是那个突然变小的地方)。

  • 贪心观察: 比较 nums[mid]nums[right]
  • 如果 nums[mid] > nums[right]:说明最小值一定在 mid 的右边(因为右边更小,打破了升序规律)。
  • 如果 nums[mid] < nums[right]:说明从 midright 是升序的,最小值可能就是 mid 或者在 mid 左边。

代码实现:

python 复制代码
def findMin(nums):
    left, right = 0, len(nums) - 1
    while left < right: # 注意这里是 <,不是 <=
        mid = (left + right) // 2
        if nums[mid] > nums[right]:
            # 最小值在右半部分
            left = mid + 1
        else:
            # 最小值在左半部分(包含mid)
            right = mid
    return nums[left]

6. 4. 寻找两个正序数组的中位数 (困难)

题目大意:

给你两个有序数组,求合并后的中位数。要求时间复杂度为 O(log⁡(m+n))O(\log(m+n))O(log(m+n))。

零基础思路:

直接合并再找中位数是 O(m+n)O(m+n)O(m+n),不符合要求。

二分法的精髓在于:在两个数组中分别找一个切割点,使得:

  1. 左边的总个数等于右边的总个数。
  2. 左边的最大值 ≤\le≤ 右边的最小值。

由于数组是有序的,我们在短的数组上进行二分查找切口位置 i,长的数组切口位置 j 就会自动确定。

代码实现(简化思路版):

python 复制代码
def findMedianSortedArrays(nums1, nums2):
    if len(nums1) > len(nums2): # 确保 nums1 是短的
        nums1, nums2 = nums2, nums1
    
    m, n = len(nums1), len(nums2)
    left, right = 0, m
    half_len = (m + n + 1) // 2
    
    while left <= right:
        i = (left + right) // 2 # nums1 的切口
        j = half_len - i        # nums2 的切口
        
        if i < m and nums1[i] < nums2[j-1]:
            left = i + 1 # i 太小,右移
        elif i > 0 and nums1[i-1] > nums2[j]:
            right = i - 1 # i 太大,左移
        else:
            # 找到合适的切口了!
            # 确定左侧最大值
            if i == 0: max_of_left = nums2[j-1]
            elif j == 0: max_of_left = nums1[i-1]
            else: max_of_left = max(nums1[i-1], nums2[j-1])
            
            if (m + n) % 2 == 1: # 总数为奇数
                return max_of_left
            
            # 确定右侧最小值
            if i == m: min_of_right = nums2[j]
            elif j == n: min_of_right = nums1[i]
            else: min_of_right = min(nums1[i], nums2[j])
            
            return (max_of_left + min_of_right) / 2.0

总结:二分查找的精髓

  1. 确定区间: 到底是 while left <= right 还是 while left < right
    • 如果你在循环内能直接找到答案(如 nums[mid] == target),用 <=
    • 如果你是在不断逼近一个边界(如找最小值),用 <
  2. 判断逻辑: 核心在于如何排除掉那"一半"不可能的区间。
  3. 注意溢出: 在某些语言中(如 Java/C++),mid = (left + right) // 2 可能会溢出,通常写成 mid = left + (right - left) // 2(Python不需要担心这个)。
相关推荐
MicroTech20253 小时前
微算法科技(NASDAQ: MLGO)引入量子启发式算法与区块链融合的数据预测与安全传输方案
科技·算法·启发式算法
近津薪荼3 小时前
优选算法——前缀和(5):和为 K 的子数组
算法
你撅嘴真丑3 小时前
第九章-竞赛题目选讲-跳舞机
数据结构·算法
鲨鱼吃橘子4 小时前
C++刷题--递归回溯剪枝(二)
开发语言·数据结构·c++·算法·leetcode·深度优先·剪枝
plus4s11 小时前
2月12日(70-72题)
算法
m0_6727033112 小时前
上机练习第24天
算法
edisao12 小时前
序幕-内部审计备忘录
java·jvm·算法
shehuiyuelaiyuehao12 小时前
22Java对象的比较
java·python·算法
Dev7z13 小时前
滚压表面强化过程中变形诱导位错演化与梯度晶粒细化机理的数值模拟研究
人工智能·python·算法