04 二分查找
1. 在排序数组中查找元素的第一个和最后一个位置


1.1 解题思路
- 暴力做法:遍历数组,时间复杂度 \(O(n)\)
- 利用数组有序的性质二分查找:
1.2 代码实现
点击查看代码
class Solution {
public:
// 左闭右闭写法
int lower_bound(vector<int>& nums, int target) {
int n = nums.size();
int l = 0, r = n - 1;
while (l <= r) {
int m = l + (r - l) / 2;
if (nums[m] < target) {
l = m + 1; // l 指向的元素一定 >= target
} else {
r = m - 1; // r 指向的元素一定 < target
}
}
return l; // r + 1 = l
}
int upper_bound(vector<int>& nums, int target) {
int n = nums.size();
int l = 0, r = n - 1;
while (l <= r) {
int m = l + (r - l) / 2;
if (nums[m] < target + 1) {
l = m + 1; // l 指向的元素一定 >= target + 1
} else {
r = m - 1; // r 指向的元素一定 < target + 1
}
}
return r; // r + 1 = l
}
vector<int> searchRange(vector<int>& nums, int target) {
int l = lower_bound(nums, target);
if (l == nums.size() || nums[l] != target) {
return {-1, -1};
}
int r = upper_bound(nums, target);
return {l, r};
}
};
- 时间复杂度:$$O(logn)$$
- 空间复杂度:$$O(1)$$
2. 寻找峰值


2.2 解题思路
- 相邻元素必不相等。
- 数组中一定存在峰值
- 由于\(nums[n] = -\infty\),所以
nume[n - 1]有两种情况:nums[n - 1] > nums[n - 2],也即nums[n - 1]为峰顶nums[n - 1] < nums[n - 2],nume[n - 1]不是峰顶
结合条件2可知,如果我们在[0, n - 2]中没有找到峰顶元素,那么nums[n - 1]一定是峰顶。所以我们先假设nums[n - 2]不是峰顶,或者说,nums[n - 2]位于峰顶右侧。
问题:查找【目标峰顶】位置
2.3 代码实现
点击查看代码
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int n = nums.size();
// 红蓝染色法
int l = 0, r = n - 2; // 红色表示,目标峰值左侧
// 蓝色表示, 目标峰值及右侧
while (l <= r) {
int m = l + (r - l) / 2;
if (nums[m] < nums[m + 1]) {
l = m + 1;
} else {
r = m - 1;
}
}
return l;
}
};
3. 寻找旋转排序数组中的最小值



3.1 解题思路
相当经典的一道题,我还记得一个典型的例子
[1 1 0 1]和[1 0 1 1]。
不过这道题说了,数组中的元素互不相同。
我们需要一个判定方式来判断nums[mid]是在最小值的左侧还是右侧。
红蓝染色法:红色表示最小值左侧元素,蓝色表示最小值及其右侧元素。
由于最小值一定位于数组中,那么和上一道题一样,最后1个元素有两种可能:
- 是最小值
- 位于最小值右侧
无论是哪种情况,都被染成蓝色。
所以我们在0~n - 2中二分,如果nums[mid] < 最后1个数,那么
- 它可能位于一段递增的数组中
- 或者在两段递增数组中的第二段
无论是上述哪种情况,该元素都
- 要么是最小值
- 要么位于最小值右侧
无论如何,都应该被染成蓝色。
由于数组中的元素互不相同,所以如果nums[mid] > 最后1个数,那么
- 不存在只有一段递增数组的情况
- 只能在两段递增数组中的第一段
因此nums[mid]一定在最小值的左侧,应该被染成红色。
3.2 代码实现
点击查看代码
class Solution {
public:
int findMin(vector<int>& nums) {
int n = nums.size();
int l = 0, r = n - 2;
while (l <= r) {
int m = l + (r - l) / 2;
if (nums[m] < nums[n - 1]) {
r = m - 1;
} else {
l = m + 1;
}
}
return nums[l];
}
};
4. 搜索旋转排序数组



4.1 解题思路
- 两次二分
受到上一道题的启发,我们可以先在这个旋转过的数组中找到数组中的最小值,记作i。然后根据target的nums[i]的大小,进而判断target可能位于两段区间中的哪一段,再在这一段区间中二分查找(当然,也可能只有一段,这种情况更简单,直接二分查找) - 一次二分
需要分类讨论。
考虑红蓝染色法,红色表示位于target左侧,蓝色表示target及target右侧。
最后1个数一定被染成蓝色。
我们思考一下什么时候nums[mid]和它的右侧被染成蓝色。
第1种情况,如果nums[mid] > nums[n - 1],那么nums[mid]只能位于前半个递增段。
此时如果target > nums[n - 1],那么就位于同一段。
nums[mid] >= target,mid和mid右边就被染成蓝色
第2种情况,如果nums[mid] <= nums[n - 1],那么nums[mid]要么处于后半个递增段,要么整个数组都是递增的。- 此时如果
target > nums[n - 1],那么target只能位于前半个递增段(有两段),此时mid及其右边就被染成蓝色 - 如果
nums[mid] >= target,也被染成蓝色
4.2 代码实现
- 两次二分
点击查看代码
class Solution {
public:
int search_min(vector<int>& nums) {
int n = nums.size();
int l = 0, r = n - 2;
// 红色表示最小值左侧,蓝色表示最小值及其右侧
// 最后1个数要么就是最小值,要么是最小值右侧,因此一定被涂成蓝色
while (l <= r) {
int m = l + (r - l) / 2;
if (nums[m] < nums[n - 1]) {
r = m - 1; // nums[m]可能是最小值,也可能位于最小值右侧
} else {
l = m + 1;
}
}
return l; // r + 1
}
int search(vector<int>& nums, int target) {
// 两次二分
int idx = search_min(nums);
int n = nums.size();
auto binary_search = [&](int left, int right) -> int {
// 红蓝染色法:位于target左侧被染成红色,target及target右侧被染成蓝色
int right_bound = right + 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
if (left == right_bound) {
return -1;
}
if (nums[left] != target) {
return -1;
}
return left;
};
if (nums[idx] == target) {
return idx;
} else if (nums[idx] > target) {
return -1;
}
int ans = binary_search(0, idx - 1);
if (ans != -1) {
return ans;
}
return binary_search(idx + 1, n - 1);
}
};
点击查看代码
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = nums.size();
int l = 0, r = n - 2;
while (l <= r) {
int m = l + (r - l) / 2;
auto check = [&](int m) -> bool {
int x = nums[m];
if (x >= target && target > nums[n - 1]) {
return true;
}
return x <= nums[n - 1] && (target > nums[n - 1] || x >= target);
};
if (check(m)) {
r = m - 1; // m和m右边被染成蓝色
} else {
l = m + 1;;
}
}
if (l == n || nums[l] != target) {
return -1;
}
return l;
}
};
- 时间复杂度:$$O(logn)$$
- 空间复杂度:$$O(1)$$

浙公网安备 33010602011771号