须知
💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力! 👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力! 🚀 分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对C++算法感兴趣的朋友,让我们一起进步!
二分查找(Binary Search)是一种经典的算法,广泛应用于计算机科学中,尤其在处理有序数据时。其重要性体现在以下几个方面:
二分查找是一种在 有序数组 中查找目标值的算法。通过每次将查找范围缩小一半,逐步逼近目标值的位置。
定义核心:
核心思想可以概括为“分而治之”:
二分查找的基本步骤
left 和 right,初始为数组的起点和终点。mid = left + (right - left) / 2 计算中间索引,避免溢出。right = mid - 1。left = mid + 1。left > right 时,说明目标值不存在,结束搜索。用于在 有序数组 中查找某个目标值。
int binarySearch(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left <= right) { // 搜索区间为 [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; // 未找到目标值
}特点:
left > right 时,说明目标值不存在。查找目标值的 最左位置。
int lowerBound(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while (left < right) { // 搜索区间为 [left, right)
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1; // 缩小到右半部分
} else {
right = mid; // 缩小到左半部分
}
}
return left; // 返回左边界
}查找目标值的 最右位置。
int upperBound(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while (left < right) { // 搜索区间为 [left, right)
int mid = left + (right - left) / 2;
if (nums[mid] <= target) {
left = mid + 1; // 缩小到右半部分
} else {
right = mid; // 缩小到左半部分
}
}
return left - 1; // 返回右边界索引
}特点:
同时查找目标值的左、右边界(适用于数组中目标值重复出现)。
vector<int> searchRange(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
// 查找左边界
int start = -1, end = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
if (left < nums.size() && nums[left] == target) {
start = left;
} else {
return {-1, -1}; // 目标值不存在
}
// 查找右边界
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;
}
}
end = right;
return {start, end};
}特点:
[start, end]。{-1, -1}。适用于需要在范围内找到满足某种条件的最优解。
int binaryAnswer(int low, int high, function<bool(int)> condition) {
while (low < high) {
int mid = low + (high - low) / 2;
if (condition(mid)) {
high = mid; // 满足条件,收缩右边界
} else {
low = mid + 1; // 不满足条件,收缩左边界
}
}
return low; // 返回最小满足条件的值
}特点:
condition(mid) 判断是否满足目标。right:
[left, right] 区间通常用于包含上下界查找。[left, right) 区间通常用于统一逻辑,更适合下界查找。mid 计算防止溢出:
mid = left + (right - left) / 2。while (left <= right) 和 while (left < right) 需区别对待。题目描述:

3.1 算法思路:
核心思想
利用 二分查找算法,在有序数组中查找目标值。每次通过比较中间值与目标值的大小,缩小搜索范围,直到找到目标值或搜索范围为空。
left 初始化为数组起点 0。right 初始化为数组终点 nums.size() - 1。left <= right 时: mid = left + (right - left) / 2(避免整数溢出)。nums[mid] == target,直接返回索引 mid。nums[mid] < target,目标值在右半部分,将 left 更新为 mid + 1。nums[mid] > target,目标值在左半部分,将 right 更新为 mid - 1。-1。class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left <= right) { // 搜索区间为 [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; // 未找到目标值
}
};示例 1:
输入:nums = [-1, 0, 3, 5, 9, 12], target = 9 输出:4
执行过程:
left = 0, right = 5。mid = (0 + 5) / 2 = 2,nums[mid] = 3 < 9,更新 left = 3。mid = (3 + 5) / 2 = 4,nums[mid] = 9 == 9,返回索引 4。边界条件分析
nums 为空,right = nums.size() - 1 = -1,循环直接跳过,返回 -1。left > right,返回 -1。暴力解法
暴力解法的核心是直接遍历数组 nums 中的每一个元素,逐一与目标值 target 进行比较,直到找到目标值或遍历结束。
-1 表示目标值不存在。class Solution {
public:
int search(vector<int>& nums, int target) {
for (int i = 0; i < nums.size(); i++) {
if (nums[i] == target) {
return i; // 找到目标值,返回索引
}
}
return -1; // 未找到目标值
}
};时间复杂度:
空间复杂度:
暴力解法的优缺点
优点:
缺点:

题目链接:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
题目描述:

4.1 算法思路:
这道题的核心是利用 二分查找 在排序数组中分别找到目标值的左右边界。
1. 核心思想
target 的元素的索引)。
target 的元素的索引)。
[-1, -1]。
2. 使用两次二分查找
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
// 查找左边界
int left = 0, right = nums.size() - 1;
int leftIndex = -1, rightIndex = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
if (left < nums.size() && nums[left] == target) {
leftIndex = left;
} else {
return {-1, -1}; // 如果左边界不存在,直接返回
}
// 查找右边界
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;
}
}
if (right >= 0 && nums[right] == target) {
rightIndex = right;
}
return {leftIndex, rightIndex};
}
};详细步骤
left = 0,right = nums.size() - 1。target 的位置: nums[mid] < target,则目标值在右侧,调整 left = mid + 1。nums[mid] >= target,则调整 right = mid - 1。nums[left] 是否等于 target,如果相等,返回 left,否则返回 -1。left = 0,right = nums.size() - 1。target 的位置: nums[mid] > target,则目标值在左侧,调整 right = mid - 1。nums[mid] <= target,则调整 left = mid + 1。nums[right] 是否等于 target,如果相等,返回 right,否则返回 -1。示例解析
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8 输出:[3,4]
执行过程:
left = 0, right = 5。mid = 2,nums[2] = 7 < 8,更新 left = 3。mid = 4,nums[4] = 8,更新 right = 3。mid = 3,nums[3] = 8,更新 right = 2。3。left = 0, right = 5。mid = 2,nums[2] = 7 < 8,更新 left = 3。mid = 4,nums[4] = 8,更新 left = 5。mid = 5,nums[5] = 10 > 8,更新 right = 4。4。输出:
[3,4]
[-1, -1]。题目链接:35. 搜索插入位置 - 力扣(LeetCode)
题目描述:

5.1 算法思路:
核心思想:二分查找
由于数组是升序排序的,因此可以使用二分查找在 O(logn) 时间复杂度内找到目标值的插入位置。
class Solution
{
public:
int searchInsert(vector<int>& nums, int target)
{
// 定义左右边界
int left = 0, right = nums.size() - 1;
// 特殊情况:如果目标值大于数组中所有元素,则插入到数组末尾
if (nums[right] < target)
return right + 1;
// 二分查找
while (left < right)
{
// 计算中间位置,避免溢出
int mid = left + (right - left) / 2;
// 如果中间值小于目标值,目标值在右半部分
if (nums[mid] < target)
left = mid + 1; // 调整左边界
else
// 如果中间值大于等于目标值,目标值可能在左半部分
right = mid; // 调整右边界
}
// 最终 left == right,返回插入位置
return left;
}
};nums[right] < target):
while (left < right) 循环,通过不断缩小区间找到目标值或插入位置。mid = left + (right - left) / 2 计算中点,避免 left + right 的溢出问题。nums[mid] < target 时,说明目标值在右半部分,更新左边界 left = mid + 1。nums[mid] == target,更新右边界 right = mid。left == right,此时 left 即为目标值所在位置或插入位置。暴力解法的核心是 直接遍历数组:
target 进行比较。这种方法时间复杂度是 O(n),适合小规模数组。
class Solution
{
public:
int searchInsert(vector<int>& nums, int target)
{
for (int i = 0; i < nums.size(); i++)
{
// 如果找到第一个大于或等于目标值的位置,返回索引
if (nums[i] >= target)
return i;
}
// 如果目标值比所有元素都大,返回数组长度
return nums.size();
}
};示例 1:
输入:nums = [1, 3, 5, 6], target = 5 输出:2
执行过程:
nums[0] = 1,1 < 5,继续。nums[1] = 3,3 < 5,继续。nums[2] = 5,5 >= 5,返回索引 2。时间复杂度:
0,时间复杂度为 O(1)。空间复杂度:
优缺点分析
优点:
缺点:
left 即为插入位置。题目链接:69. x 的平方根 - 力扣(LeetCode)
题目描述:

我们要找到满足以下条件的最大整数 y:
利用 二分查找 来寻找平方根:
[1, x](因为 x\sqrt{x}x 最大不会超过 x 本身)。
mid,计算 mid2 与 x 比较:
left = mid。right = mid - 1。left 指向平方根的整数部分。
class Solution
{
public:
int mySqrt(int x)
{
if (x < 1) return 0; // 特殊情况处理
int left = 1, right = x; // 定义二分查找的边界
while (left < right)
{
long long mid = left + (right - left + 1) / 2; // 向上取整计算中点
if (mid * mid > x)
right = mid - 1; // 排除右半部分
else
left = mid; // 更新左边界,保留 mid
}
return left; // 最终返回平方根的整数部分
}
};版本2:
class Solution
{
public:
int mySqrt(int x)
{
if (x == 0) return 0; // 直接处理特殊情况
int left = 1, right = x;
while (left <= right) // 使用闭区间 [left, right]
{
int mid = left + (right - left) / 2;
if (mid <= x / mid) // 避免溢出
left = mid + 1; // 更新左边界
else
right = mid - 1; // 更新右边界
}
return right; // 返回右边界,即平方根整数部分
}
};[1, x],因为平方根不会小于 1 且不会大于 x(对于 x≥1)。mid = left + (right - left + 1) / 2 来计算中点,避免直接加法可能导致的溢出。right = mid - 1,排除当前的 mid 和右侧部分。left = mid,保留当前的 mid。left == right,此时 left 即为平方根的整数部分。暴力解法
class Solution
{
public:
int mySqrt(int x)
{
if (x == 0) return 0; // 特殊情况处理
int y = 0;
while ((long long)y * y <= x) // 防止溢出
{
y++;
}
return y - 1; // 返回满足条件的最大整数
}
};时间复杂度
0 遍历到 x\sqrt{x},因此时间复杂度为 O(x\sqrt{x})。
空间复杂度
y 和计算,不需要额外空间。优缺点分析
优点
缺点
long long 类型,会因为 y2 的溢出问题导致错误结果。
[1, x] 中寻找满足 y2≤x 的最大整数 y。x = 0 和 x = 1 等特殊情况。mid <= x / mid 避免溢出问题。通过上述例题分析可以看出,二分查找的核心用途是 快速缩小搜索范围,解决有序数据、边界条件和单调问题等场景。在实际问题中,熟练掌握二分查找模板和变体是解决高效算法问题的关键。
路虽远,行则将至;事虽难,做则必成
亲爱的读者们,下一篇文章再会!!!