🌌 二分查找:当折叠的纸张遇见算法的诗意
"在有序的世界里,每一次对分都是一次智慧的修行"
📜 缘起:纸张的奇幻旅程
让我们开始一场思想的实验。取一张寻常的A4纸,厚度约 0.1毫米。现在,想象你拥有神奇的力量,能够将它对折100次。
| 折叠次数 | 厚度 | 诗意描述 |
|---|---|---|
| 🌀 第1次 | 0.2mm | 尚在掌握之中 |
| 🌀 第10次 | 10cm | 如书本般厚重 |
| 🌙 第42次 | 44万公里 | 足以抵达月球 |
| ☀️ 第51次 | 2.2亿公里 | 已超越太阳 |
| 🌌 第100次 | 1.27×10²³公里 | 在宇宙尺度绵延 |
这不仅是数学的奇迹,更是二分思想的完美诠释:每一次用心的折叠,都在开启指数增长的无限可能。
💫 二分查找:算法世界中的优雅舞者
在编程的宇宙里,二分查找便是这样一位优雅的舞者。她只在有序的舞台上绽放,却能用最少的步数,找到心中的答案。
🎯 算法的诗意表达
int binarySearch(int[] arr, int target) {
int left = 0; // 🚶 启程于起点,区间左边界
int right = arr.length - 1; // 🏔️ 遥望着远方,区间右边界
while (left <= right) { // 💫 只要希望还在,左边界要小于右边界
int mid = left + (right - left) / 2; // ⚖️ 取中而立,不偏不倚,这个用来和目标值作比较
if (arr[mid] == target) {
return mid; // 🎉 蓦然回首,那人却在灯火阑珊处,找到目标值,return 结果
} else if (arr[mid] < target) {
left = mid + 1; // ➡️ 目标在前方,迈步向前,目标值比当前值大,说明在更右侧,将左边界右移,移到当前值的位置
} else {
right = mid - 1; // ⬅️ 目标在身后,转身追寻,目标值比当前值小,说明在更左侧,将右边界左移,移到当前值的位置
}
}
return -1; // 😔 寻寻觅觅,终是缘悭一面,没有找到,唉,遗憾了!!!
}
🎨 图解二分查找:一场精准的寻宝之旅
让我们通过寻找数字 23 的旅程,可视化二分查找的精妙过程:
🔍 寻找数字 23
初始数组: [2, 5, 8, 12, 16, 23, 38, 45, 67, 89]
目标值: 23
graph TD
A[初始: 0-9, mid=4, val=16] --> B{16 < 23?}
B -->|是| C[向右: 5-9]
B -->|否| D[向左: 0-3]
C --> E{45 > 23?}
E -->|是| F[向左: 5-6]
E -->|否| G[向右: 8-9]
F --> H{23 = 23?}
H -->|是| I[找到目标!]
H -->|否| J[继续]
📊 搜索过程详解
| 步骤 | 区间 | 中间索引 | 中间值 | 比较结果 | 动作 |
|---|---|---|---|---|---|
| 1️⃣ | [0, 9] | 4 | 16 | 16 < 23 | ➡️ 向右搜索 |
| 2️⃣ | [5, 9] | 7 | 45 | 45 > 23 | ⬅️ 向左搜索 |
| 3️⃣ | [5, 6] | 5 | 23 | 23 == 23 | 🎯 找到目标 |
✨ 仅用3次比较,就在10个元素中找到了目标! 线性搜索最坏需要10次,二分查找展现了其对数级的高效本质。
🌟 实际问题:寻找生命中的"合格元素"
💭 问题的哲学思考
我们常常在问:在生命的序列中,有多少个"我",能够找到足够多的榜样引领前行?
用算法的语言表述:给定数组nums和整数k,统计有多少元素满足——至少存在k个元素,如明灯般照亮前路,严格大于它。
🌈 示例的启示
// 示例1: 多样化的世界
nums = [3, 1, 2], k = 1
✅ 元素1和2都找到了引领自己的光芒
🎯 答案: 2
// 示例2: 同质化的世界
nums = [5, 5, 5], k = 2
❌ 所有人都站在同一高度,失去了相互照耀的可能
🎯 答案: 0
⚡ 解决方案对比
🐌 朴素解法:力竭的守望者
/**
* 🌿 朴素解法:力竭的守望者
* 如同让每个人亲自数尽满天繁星
* 虽真诚,却难免力不从心
*
* @param nums 众生序列,如星河般排列
* @param k 期待的引路者数量
* @return 找到足够引路者的灵魂个数
*/
public int countElementsBruteForce(int[] nums, int k) {
int count = 0; // 🎯 记录合格灵魂的数量
// 🌀 为序列中的每个灵魂,寻找前方的引路者
for (int i = 0; i < nums.length; i++) {
int greaterCount = 0; // ✨ 记录比当前灵魂更明亮的星辰
// 🌌 遍历整片星空,清点引路者
for (int j = 0; j < nums.length; j++) { // 🔁 双重循环的宿命
if (nums[j] > nums[i]) {
greaterCount++; // 💫 又发现一颗引路星辰
}
}
// 🌟 如果找到了足够的引路者
if (greaterCount >= k) {
count++; // 🎉 这个灵魂是合格的,值得庆贺
}
}
return count; // 🌈 返回所有找到光明的灵魂
}
⏰ 时间复杂度: O(n²)
💔 当 n=10⁵ 时: 需要 10¹⁰ 次比较,如数尽满天繁星,终将力竭而止。
🚀 二分解法:智慧的引路人
public int countElements(int[] nums, int k) {
int[] sorted = nums.clone();
Arrays.sort(sorted); // 🌟 先建立秩序
int n = nums.length;
int count = 0;
for (int x : nums) {
// 🔍 在有序世界中寻找引路者
int lo = 0, hi = n;
while (lo < hi) {
int mid = lo + (hi - lo) / 2; // ⚖️ 中庸之道
if (sorted[mid] <= x) {
lo = mid + 1; // ➡️ 光明在右
} else {
hi = mid; // ⬅️ 光明在左
}
}
int greaterCount = n - lo; // ✨ 计算引路者数量
if (greaterCount >= k) count++; // ✅ 合格元素
}
return count;
}
⏰ 时间复杂度: O(n log n)
💖 效率提升: 从混沌到有序的智慧跃迁
🎯 图解上界查找:寻找第一个超越者
让我们可视化在排序数组中寻找第一个大于目标值的过程:
排序数组: [2, 5, 8, 12, 16, 23, 23, 23, 38, 45]
目标值: 23
graph LR
A[初始: lo=0 hi=9 mid=4 val=16] --> B[16<=23: lo=5]
B --> C[第二轮: lo=5 hi=9 mid=7 val=23]
C --> D[23<=23: lo=8]
D --> E[第三轮: lo=8 hi=9 mid=8 val=38]
E --> F[38>23: hi=8]
F --> G[找到位置8]
🌈 二分思想的万千化身
| 变体类型 | 功能描述 | 应用场景 |
|---|---|---|
| 🎯 精确查找 | 找到目标值的确切位置 | 基础搜索 |
| 📍 下界查找 | 找到第一个≥目标值的位置 | 范围查询 |
| 🚀 上界查找 | 找到第一个 > 目标值的位置 | 本文问题 |
// 🚀 上界查找:寻找第一个超越目标的光芒
int findFirstGreater(int[] arr, int target) {
int left = 0, right = arr.length;
while (left < right) {
int mid = left + (right - left) / 2; // ⚖️ 平衡之美
if (arr[mid] <= target) {
left = mid + 1; // ➡️ 目标尚远,继续前行
} else {
right = mid; // ⬅️ 目标已近,细细分辨
}
}
return left; // 🌟 返回第一个超越的索引
}
💫 算法如诗:应用的艺术殿堂
| 应用领域 | 诗意描述 | 技术实现 |
|---|---|---|
| 数学求根 | 在连续函数中寻找命运的转折点 | 二分法求零点 |
| 最优化 | 在约束框架内追求极致的完美 | 二分答案 |
| 范围查询 | 在有序序列中定位生命坐标 | 本文实例 |
| 数据库索引 | 为海量数据建立快速通途 | B-tree索引 |
🌟 结语:算法的诗意栖居
让我们再次回望那张折叠100次的纸,它诉说着:
"指数增长的力量,源于每一次用心的对分选择"
二分查找,这个看似简单的算法,蕴含着深刻的生命哲理:
- 🌱 排序是前提:先建立内在秩序,方能高效前行
- 🧠 分治是智慧:面对复杂,懂得分解,方见真章
- ⚡ 对数是效率:用最少的步骤,抵达最远的彼岸
在编程的道路上,当"有序"与"查找"相逢,二分查找便是那首最美的诗,等待我们用代码书写生命的算法。
🌙 夜深人静时思考:
如果比较条件从"严格大于"变为"大于等于",
我们该如何重新定义心中的那杆秤?
这不仅是算法的调整,
更是对边界理解的深化。
📚 延伸阅读
graph TB
A[二分查找] --> B[基础变体]
A --> C[应用扩展]
B --> B1[精确查找]
B --> B2[下界查找]
B --> B3[上界查找]
C --> C1[二分答案]
C --> C2[旋转数组搜索]
C --> C3[峰值查找]
每种变体都在特定的场景下绽放独特的光彩,等待你去探索发现。