
🌟《小侦探找宝藏:二分查找大冒险》
🏰 第一幕:宝藏在哪里?
1、有一天,小侦探小明来到一条已经排好序的宝藏街:
1 3 5 7 9 11 13 15
2、每个数字代表一个宝箱编号(从小到大排好序!)
3、🎯任务:
找到编号为 11 的宝箱!
🤔 第二幕:普通方法(慢!)
1、小明一开始是这样找的:
👉 使用枚举法,一个一个找:
1 → 3 → 5 → 7 → 9 → 11 ✔
2、😫问题:太慢了!
🚀 第三幕:聪明办法(二分查找登场!)
1、小明突然想到:
💡"既然已经排好序,我是否可以看看中间数字是大还是小!"
2、🧠 核心思想(非常重要!)
👉 每次都检查中间位置
-
如果中间值 == 目标 → 找到了 🎉
-
如果中间值 < 目标 → 去右边找 👉
-
如果中间值 > 目标 → 去左边找 👈
🎯 第四幕:一步一步找宝藏!
1、我们来找 11
数组:
[1, 3, 5, 7, 9, 11, 13, 15]
2、🥇 第1次
左:0 (数组索引为0)
右:7 (数组索引为7)
(1)👉 中间:
mid = (0 + 7) / 2 = 3
(2)👉 数值:
a[3] = 7
(3)😮 7 < 11
👉 去右边!
3、🥈 第2次
左:4 (数组索引为4)
右:7 (数组索引为7)
(1)👉 中间:
mid = (4 + 7) / 2 = 5
(2)👉 数值:
a[5] = 11 ✔
(3)🎉 找到了!
4、🧾 总结
👉 二分查找四步走:
1️⃣ 左右边界定好(left, right)
2️⃣ 找中间(mid)
3️⃣ 比大小
4️⃣ 缩范围
🧩 第五幕:参考程序:
#include <iostream>
using namespace std;
int main()
{
int a[] = {1,3,5,7,9,11,13,15};
int n = 8;
int target = 11;
int left = 0, right = n - 1;
while(left <= right)
{
int mid = (left + right) / 2;
if(a[mid] == target)
{
cout << "找到了!位置是:" << mid << endl;
break;
}
else if(a[mid] < target)
{
left = mid + 1; // 去右边
}
else
{
right = mid - 1; // 去左边
}
}
return 0;
}
⚠️ 第六幕:容易犯的错误!
1、❌ 错误1:数组没有排序!
👉 二分查找必须是:
有序数组!(从小到大或从大到小)
2、❌ 错误2:死循环
👉 本题循环条件:
left <= right
3、❌ 错误3:更新错误
👉 本题更新为:
left = mid + 1
right = mid - 1
🚀 第七幕:二分查找有多快?
1、假设有 1000 个数:
-
普通查找:最多找 1000 次 😫
-
二分查找:最多找 10 次!😲
2、👉 因为每次都"砍一半"!
3、时间复杂度是: O(logn)
🎯 简单理解(一句话)
👉 二分查找就是:
💡每次砍掉一半范围的查找方法!
我们接下来继续进行:
🌟《小侦探的升级任务:寻找"最左"和"最右"的宝藏》
🏰 第一幕:奇怪的宝藏街
1、这次,小明来到了一条有重复数字的街道:
1 2 2 2 3 4 4 5
🎯任务升级:
👉 找数字 2 的第一个位置(最左边)
👉 找数字 2 的最后一个位置(最右边)
2、🤔 为什么普通二分不够?
普通二分只能找到:
👉 "某一个 2"
但不知道是不是:
-
最左的 ❌
-
最右的 ❌
3、🧠 核心思想(关键理解!)
👉 不要一找到就停!
而是:
-
继续往左缩(找左边界)
-
继续往右缩(找右边界)
🎯 第二幕:找左边界(最左的2)
1、🧭 思路
👉 即使找到了 target,也要继续往左找!
2、🪄 过程演示
1 2 2 2 3 4 4 5
↑
找到 2 后:
👉 继续往左缩!
3、🧾 C++模板(左边界)
int left_bound(int a[], int n, int target)
{
int left = 0, right = n - 1;
int ans = -1;
while(left <= right)
{
int mid = (left + right) / 2;
if(a[mid] == target)
{
ans = mid; // 记录答案
right = mid - 1; // 继续往左找!
}
else if(a[mid] < target)
{
left = mid + 1;
}
else
{
right = mid - 1;
}
}
return ans;
}
🎯 一句话总结
👉 找到后:
往左缩!(right = mid - 1)
🎯 第三幕:找右边界(最右的2)
🧭 思路
👉 找到 target 后,继续往右找!
🧾 C++模板(右边界)
int right_bound(int a[], int n, int target)
{
int left = 0, right = n - 1;
int ans = -1;
while(left <= right)
{
int mid = (left + right) / 2;
if(a[mid] == target)
{
ans = mid; // 记录答案
left = mid + 1; // 继续往右找!
}
else if(a[mid] < target)
{
left = mid + 1;
}
else
{
right = mid - 1;
}
}
return ans;
}
🎯 一句话总结
👉 找到后:
往右缩!(left = mid + 1)
⚖️ 对比总结:
| 类型 | 找到后怎么做 |
|---|---|
| 普通二分 | 直接 return |
| 左边界 | right = mid - 1 |
| 右边界 | left = mid + 1 |
🧩 第四幕:记忆口诀
🎯 找左边界:
👉 "找到了别停,继续往左挤!"
🎯 找右边界:
👉 "找到了别停,继续往右挤!"
🚨 第五幕:常见坑!
❌ 坑1:找到就 return
👉 错!那只是普通二分!
❌ 坑2:忘记记录答案
👉 必须:
ans = mid;
❌ 坑3:写反方向
👉 左边界:
right = mid - 1 ✔
👉 右边界:
left = mid + 1 ✔
🎁 第六幕:参考代码
#include <iostream>
using namespace std;
int left_bound(int a[], int n, int target)
{
int left = 0, right = n - 1;
int ans = -1;
while(left <= right)
{
int mid = (left + right) / 2;
if(a[mid] == target)
{
ans = mid;
right = mid - 1;
}
else if(a[mid] < target)
{
left = mid + 1;
}
else
{
right = mid - 1;
}
}
return ans;
}
int right_bound(int a[], int n, int target)
{
int left = 0, right = n - 1;
int ans = -1;
while(left <= right)
{
int mid = (left + right) / 2;
if(a[mid] == target)
{
ans = mid;
left = mid + 1;
}
else if(a[mid] < target)
{
left = mid + 1;
}
else
{
right = mid - 1;
}
}
return ans;
}
int main()
{
int a[] = {1,2,2,2,3,4,4,5};
int n = 8;
cout << "左边界:" << left_bound(a, n, 2) << endl;
cout << "右边界:" << right_bound(a, n, 2) << endl;
return 0;
}
🚀 总结:
👉 普通二分:找"有没有"
👉 左边界:找"最左"
👉 右边界:找"最右"