二分查找六题通关:从标准模板到旋转数组(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] < target,left = 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 的二分不难。