二分查找六题通关:从标准模板到旋转数组(Python + C++)

二分查找六题通关:从标准模板到旋转数组(Python + C++)

二分查找是算法中最基础也最易错的技巧之一。本文整理了6道经典题目,每道题包含:题目描述、解题思路、图解(文本示意)、Python代码、C++代码、复杂度分析。掌握这些,二分查找类题目基本通关。


📌 题目清单

题号 题目 核心考点
704 二分查找 标准模板
35 搜索插入位置 二分边界
34 在排序数组中查找元素的第一个和最后一个位置 两次二分(左右边界)
33 搜索旋转排序数组 二分(判断有序部分)
153 寻找旋转排序数组中的最小值 二分(与右端点比较)
69 x 的平方根 二分 / 牛顿迭代

1. 二分查找(LeetCode 704)

题目描述

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target,写一个函数搜索 nums 中的 target,如果存在返回下标,否则返回 -1

示例

输入:nums = [-1,0,3,5,9,12], target = 9 → 输出:4

输入:nums = [-1,0,3,5,9,12], target = 2 → 输出:-1

解题思路

标准的二分查找模板:

  • 初始化左右指针 left = 0, right = len(nums)-1
  • left <= right 时循环:
    • 计算 mid = left + (right - left) // 2(防止溢出)。
    • 如果 nums[mid] == target,返回 mid
    • 如果 nums[mid] < targetleft = mid + 1
    • 否则 right = mid - 1
  • 循环结束返回 -1

图解

复制代码
nums = [-1,0,3,5,9,12], target=9
left=0, right=5, mid=2 -> nums[2]=3 < 9 → left=3
left=3, right=5, mid=4 -> nums[4]=9 == 9 → 返回4

Python代码

python 复制代码
def search(nums, target):
    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 -1

C++代码

cpp 复制代码
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) return mid;
            else if (nums[mid] < target) left = mid + 1;
            else right = mid - 1;
        }
        return -1;
    }
};

复杂度分析

  • 时间复杂度:O(log n)。
  • 空间复杂度:O(1)。

2. 搜索插入位置(LeetCode 35)

题目描述

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

示例

输入:nums = [1,3,5,6], target = 5 → 输出:2

输入:nums = [1,3,5,6], target = 2 → 输出:1

输入:nums = [1,3,5,6], target = 7 → 输出:4

解题思路

  • 二分查找,若找到目标直接返回下标。
  • 如果没找到,循环结束时的 left 就是插入位置(因为 left 指向第一个大于 target 的位置)。
  • 也可以统一用双闭区间模板,最后返回 left

图解

复制代码
nums = [1,3,5,6], target=2
left=0, right=3, mid=1 -> nums[1]=3 > 2 → right=0
left=0, right=0, mid=0 -> nums[0]=1 < 2 → left=1
left=1 > right 结束,返回 left=1

Python代码

python 复制代码
def searchInsert(nums, target):
    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

C++代码

cpp 复制代码
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) return mid;
            else if (nums[mid] < target) left = mid + 1;
            else right = mid - 1;
        }
        return left;
    }
};

复杂度分析

  • 时间复杂度:O(log n)。
  • 空间复杂度:O(1)。

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

题目描述

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。如果不存在,返回 [-1, -1]

示例

输入:nums = [5,7,7,8,8,10], target = 8 → 输出:[3,4]

输入:nums = [5,7,7,8,8,10], target = 6 → 输出:[-1,-1]

解题思路

  • 写一个二分函数 findLeft 找左边界,findRight 找右边界。
  • 左边界:当 nums[mid] >= target 时移动右边界,最终 left 是第一个等于 target 的位置(若存在)。
  • 右边界:当 nums[mid] <= target 时移动左边界,最终 right 是最后一个等于 target 的位置。
  • 特殊处理:如果左边界越界或值不等于 target,返回 [-1,-1]

图解

复制代码
nums = [5,7,7,8,8,10], target=8
找左边界:
left=0,right=5,mid=2->7<8 → left=3
left=3,right=5,mid=4->8>=8 → right=3
left=3,right=3,mid=3->8>=8 → right=2
left=3 > right 结束,left=3
找右边界:
left=0,right=5,mid=2->7<=8 → left=3
left=3,right=5,mid=4->8<=8 → left=5
left=5,right=5,mid=5->10<=8? 否 → right=4
left=5 > right 结束,right=4
结果[3,4]

Python代码

python 复制代码
def searchRange(nums, target):
    def findLeft():
        left, right = 0, len(nums) - 1
        while left <= right:
            mid = left + (right - left) // 2
            if nums[mid] >= target:
                right = mid - 1
            else:
                left = mid + 1
        return left

    def findRight():
        left, right = 0, len(nums) - 1
        while left <= right:
            mid = left + (right - left) // 2
            if nums[mid] <= target:
                left = mid + 1
            else:
                right = mid - 1
        return right

    l = findLeft()
    r = findRight()
    if l <= r and l < len(nums) and nums[l] == target:
        return [l, r]
    return [-1, -1]

C++代码

cpp 复制代码
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int left = findLeft(nums, target);
        int right = findRight(nums, target);
        if (left <= right && left < nums.size() && nums[left] == target) {
            return {left, right};
        }
        return {-1, -1};
    }

private:
    int findLeft(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] >= target) right = mid - 1;
            else left = mid + 1;
        }
        return left;
    }
    int findRight(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] <= target) left = mid + 1;
            else right = mid - 1;
        }
        return right;
    }
};

复杂度分析

  • 时间复杂度:O(log n)。
  • 空间复杂度:O(1)。

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

题目描述

整数数组 nums 按升序排列,在某个未知点进行了旋转。给定 target,若存在返回下标,否则返回 -1。要求时间复杂度 O(log n)。

示例

输入:nums = [4,5,6,7,0,1,2], target = 0 → 输出:4

输入:nums = [4,5,6,7,0,1,2], target = 3 → 输出:-1

解题思路

  • 在二分过程中,总能判断哪一半是有序的。
  • 如果 nums[left] <= nums[mid],则左半有序:
    • target 在左半范围内,则 right = mid - 1;否则 left = mid + 1
  • 否则右半有序:
    • target 在右半范围内,则 left = mid + 1;否则 right = mid - 1

图解

复制代码
nums = [4,5,6,7,0,1,2], target=0
left=0,right=6,mid=3 -> nums[3]=7, left有序[4,5,6,7], target=0不在其中 → left=4
left=4,right=6,mid=5 -> nums[5]=1, 右半有序[0,1,2], target=0在其中 → right=4
left=4,right=4,mid=4 -> nums[4]=0 → 返回4

Python代码

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

C++代码

cpp 复制代码
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) return mid;
            if (nums[left] <= nums[mid]) {
                if (nums[left] <= target && target < nums[mid]) right = mid - 1;
                else left = mid + 1;
            } else {
                if (nums[mid] < target && target <= nums[right]) left = mid + 1;
                else right = mid - 1;
            }
        }
        return -1;
    }
};

复杂度分析

  • 时间复杂度:O(log n)。
  • 空间复杂度:O(1)。

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

题目描述

已知一个长度为 n 的升序数组在某个点旋转,找出数组中的最小元素。假设数组中不存在重复元素。

示例

输入:nums = [3,4,5,1,2] → 输出:1

输入:nums = [4,5,6,7,0,1,2] → 输出:0

解题思路

  • 二分查找,每次与右端点比较。
  • 如果 nums[mid] > nums[right],说明最小值在右半(mid 在左半较大区间),left = mid + 1
  • 否则最小值在左半(包括 mid),right = mid
  • 最后 nums[left] 即为最小值。

图解

复制代码
nums = [3,4,5,1,2]
left=0,right=4,mid=2 -> nums[2]=5 > nums[4]=2 → left=3
left=3,right=4,mid=3 -> nums[3]=1 <= 2 → right=3
left=3,right=3 → 返回 nums[3]=1

Python代码

python 复制代码
def findMin(nums):
    left, right = 0, len(nums) - 1
    while left < right:
        mid = left + (right - left) // 2
        if nums[mid] > nums[right]:
            left = mid + 1
        else:
            right = mid
    return nums[left]

C++代码

cpp 复制代码
class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0, right = nums.size() - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > nums[right]) left = mid + 1;
            else right = mid;
        }
        return nums[left];
    }
};

复杂度分析

  • 时间复杂度:O(log n)。
  • 空间复杂度:O(1)。

6. x 的平方根(LeetCode 69)

题目描述

实现 int sqrt(int x) 函数,计算并返回 x 的平方根,其中 x 是非负整数。返回整数部分,小数部分舍去。

示例

输入:4 → 输出:2

输入:8 → 输出:2(因为 2^2=4, 3^2=9 > 8)

解题思路

  • 二分查找范围 [0, x]
  • 找满足 mid*mid <= x 的最大 mid
  • 注意用 mid <= x // mid 避免溢出。
  • 也可用牛顿迭代法。

图解

复制代码
x=8
left=0,right=8,mid=4 -> 4*4=16>8 → right=3
left=0,right=3,mid=1 -> 1<=8 → left=2
left=2,right=3,mid=2 -> 4<=8 → left=3
left=3,right=3,mid=3 -> 9>8 → right=2
left=3>right结束,返回right=2

Python代码(二分法)

python 复制代码
def mySqrt(x):
    if x < 2:
        return x
    left, right = 0, x
    while left <= right:
        mid = left + (right - left) // 2
        if mid == x // mid:
            return mid
        elif mid < x // mid:
            left = mid + 1
        else:
            right = mid - 1
    return right

C++代码(二分法)

cpp 复制代码
class Solution {
public:
    int mySqrt(int x) {
        if (x < 2) return x;
        int left = 0, right = x;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (mid == x / mid) return mid;
            else if (mid < x / mid) left = mid + 1;
            else right = mid - 1;
        }
        return right;
    }
};

Python代码(牛顿迭代,可选)

python 复制代码
def mySqrt(x):
    if x < 2:
        return x
    r = x
    while r > x // r:
        r = (r + x // r) // 2
    return r

复杂度分析

  • 时间复杂度:O(log x)(二分法)或 O(log x)(牛顿迭代,但常数更小)。
  • 空间复杂度:O(1)。

🎯 总结

题目 核心技巧 时间复杂度 空间复杂度
704. 二分查找 标准闭区间模板 O(log n) O(1)
35. 搜索插入位置 返回 left 即为插入位 O(log n) O(1)
34. 查找首末位置 两次二分找左右边界 O(log n) O(1)
33. 搜索旋转数组 判断有序半区 O(log n) O(1)
153. 旋转数组最小值 与右端点比较 O(log n) O(1)
69. x的平方根 二分查找整数部分 O(log x) O(1)

二分查找的关键在于明确搜索区间边界收缩条件。对于旋转数组,要善于利用"至少有一半是有序的"这一性质。多练习边界条件,写出 bug-free 的二分不难。

相关推荐
驼同学.1 小时前
【求职季】LeetCode Hot 100 渐进式扫盲手册(Python版)
python·算法·leetcode
Kiyra1 小时前
LLM 的 JSON 不靠谱:结构化输出的重试与修复实战
开发语言·python·json
u0110225121 小时前
SQL如何利用聚合函数进行库存预测_历史数据分组汇总
jvm·数据库·python
Trouville011 小时前
学习tips:一些可以持续学习的网络体系教程
python·深度学习
无所事事O_o1 小时前
IntelliJ IDEA 无法识别 Maven SNAPSHOT 依赖,但 Maven 编译正常
java
神明9311 小时前
数据库模型设计实战:如何导出数据库完整数据字典_规范化流程
jvm·数据库·python
老纪1 小时前
SQL中如何查找包含关键字的行:FULLTEXT全文索引检索
jvm·数据库·python
茉莉玫瑰花茶1 小时前
LangGraph 入门教程:构建 AI 工作流 [ 案例二 ]
开发语言·人工智能·python
yaoxin5211231 小时前
403. Java 文件操作基础 - 写入二进制文件
java·开发语言·python