# 二分查找:当纸张折叠100次,遇见算法的诗意

129 阅读6分钟

🌌 二分查找:当折叠的纸张遇见算法的诗意

"在有序的世界里,每一次对分都是一次智慧的修行"

📜 缘起:纸张的奇幻旅程

让我们开始一场思想的实验。取一张寻常的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]41616 < 23➡️ 向右搜索
2️⃣[5, 9]74545 > 23⬅️ 向左搜索
3️⃣[5, 6]52323 == 23🎯 找到目标

✨ 仅用3次比较,就在10个元素中找到了目标! 线性搜索最坏需要10次,二分查找展现了其对数级的高效本质。


🌟 实际问题:寻找生命中的"合格元素"

💭 问题的哲学思考

我们常常在问:在生命的序列中,有多少个"我",能够找到足够多的榜样引领前行?

用算法的语言表述:给定数组nums和整数k,统计有多少元素满足——至少存在k个元素,如明灯般照亮前路,严格大于它

🌈 示例的启示

// 示例1: 多样化的世界
nums = [3, 1, 2], k = 1
✅ 元素12都找到了引领自己的光芒
🎯 答案: 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[峰值查找]

每种变体都在特定的场景下绽放独特的光彩,等待你去探索发现。