力扣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,判断在不在矩阵里。

零基础思路:

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

  • 假设矩阵是 m 行 n 列,总共有 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]:说明从 mid 到 right 是升序的,最小值可能就是 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不需要担心这个)。
相关推荐
nhdh37 分钟前
B+树揭秘:MySQL索引核心原理全解析
数据结构·b树·mysql
海上小飞龙1 小时前
【KMP算法-下篇】同一道题:Java 库函数 2600 微秒,手写 KMP 16 微秒
java·开发语言·算法
青少儿编程课堂1 小时前
后缀自动机解析:本质不同子串与最长重复片段统计
c++·python·算法·bfs·信息学竞赛
朝朝辞暮i1 小时前
C++ 第 12 课:局部变量、作用域、变量生命周期
开发语言·c++·算法
乐迪信息3 小时前
港口船舶逆行怎么管?AI防爆摄像机搭载智能检测算法
大数据·人工智能·深度学习·算法·计算机视觉
Sirens.3 小时前
Java并发锁详解:六类锁策略与 synchronized 底层原理
java·前端·算法
和裕4 小时前
全纸结构重型纸箱能否满足 1 吨以上设备出口熏蒸豁免要求?通关合规性全解析
大数据·运维·网络·人工智能·算法
tangguofeng4 小时前
不带头结点的链队操作集(不含InitFlag)(C语言版)
算法
苏打豆6 小时前
CRISP源码阅读——基于ROS2力矩反馈控制器与速度加速度零空间
c++·线性代数·算法·矩阵·机器人
蛋蛋的就会顺顺的6 小时前
hot100——矩阵
java·数据结构·算法·leetcode·力扣