二分查找那点事儿

二分查找你针对有序序列而言的一种查找算法,关于二分查找有一下的几类问题:

1. 给定一个有序(不降序)数组arr,求任意一个i使得arr[i]等于v,不存在则返回-1

2.给定一个有序(不降序)数组arr,求最小的i使得arr[i]等于v,不存在则返回-1

3.给定一个有序(不降序)数组arr,求最大的i使得arr[i]等于v,不存在则返回-1

4.给定一个有序(不降序)数组arr,求最大的i使得arr[i]小于v,不存在则返回-1

5.给定一个有序(不降序)数组arr,求最小的i使得arr[i]大于v,不存在则返回-1

注意的是:

1,在求二分查找的中间点时没有使用

midIndex = (minIndex + maxIndex) / 2

是因为,以免 minIndex + maxIndex之后会导致溢出而出现错误。

2,注意循环的循环终止条件及边界元素的判定

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
/*
//给定一个有序(不降序)数组arr,求最大的i使得arr[i]等于v,不存在则返回-1
int bisearch(char arr[][10], int begin, int end, char *v)
{
    int minIndex = begin;
    int maxIndex = end;
    int midIndex;

    while(minIndex < maxIndex - 1) {
        midIndex = minIndex + (maxIndex - minIndex) / 2;

        if(strcmp(arr[midIndex], v) <= 0)
            minIndex = midIndex;
        else
            maxIndex = midIndex;
    }

    if(!strcmp(arr[maxIndex], v))
        return maxIndex;
    else if(!strcmp(arr[minIndex], v))
        return minIndex;
    else
        return -1;
}
*/

/*
//一个有序(不降序)数组arr,求任意一个i使得arr[i]等于v,不存在则返回-1
int bisearch(char (*arr)[10], int begin, int end, char *v)
{
    int minIndex = begin;
    int maxIndex = end;
    int midIndex;
    while(minIndex < maxIndex) {
       midIndex = minIndex + (maxIndex - minIndex) / 2;
       if(strcmp(*(arr + midIndex), v) < 0)
           minIndex = midIndex + 1;
       else if(strcmp(*(arr + midIndex), v) > 0)
           maxIndex = midIndex - 1;
       else
           return midIndex;
    }

    if(!strcmp(*(arr + minIndex), v))
        return minIndex;

    return -1;
}
*/

/*
//给定一个有序(不降序)数组arr,求最小的i使得arr[i]等于v,不存在则返回-1
int bisearch(char (*arr)[10], int begin, int end, char *v)
{
    int minIndex = begin;
    int maxIndex = end;
    int midIndex;

    while(minIndex < maxIndex - 1) {
        midIndex = minIndex + (maxIndex - minIndex) / 2;
        if(strcmp(*(arr + midIndex), v) < 0)
            minIndex = midIndex;
        else
            maxIndex = midIndex;
    }

    if(!strcmp(*(arr + minIndex), v))
        return minIndex;
    else if(!strcmp(*(arr + maxIndex), v))
        return maxIndex;
    else
        return -1;
}
*/

/*
//给定一个有序(不降序)数组arr,求最大的i使得arr[i]小于v,不存在则返回-1
int bisearch(char (*arr)[10], int begin, int end, char *v)
{
    int minIndex = begin;
    int maxIndex = end;
    int midIndex;

    while(minIndex < maxIndex - 1) {
        midIndex = minIndex + (maxIndex - minIndex) / 2;
        if(strcmp(*(arr + midIndex), v) < 0)
            minIndex = midIndex;
        else
            maxIndex = midIndex;
    }

    if(strcmp(*(arr + maxIndex), v) < 0)
        return maxIndex;
    else if(strcmp(*(arr + minIndex), v) < 0)
        return minIndex;
    else
        return -1;
}
*/

//给定一个有序(不降序)数组arr,求最小的i使得arr[i]大于v,不存在则返回-1
int bisearch(char (*arr)[10], int begin, int end, char *v)
{
    int minIndex = begin;
    int maxIndex = end;
    int midIndex;

    while(minIndex < maxIndex - 1) {
        midIndex = minIndex + (maxIndex - minIndex) / 2;
        if(strcmp(*(arr + midIndex), v) <= 0)
            minIndex = midIndex;
        else
            maxIndex = midIndex;
    }

    //从小数开始判断
    if(strcmp(*(arr + minIndex), v) > 0)
        return minIndex;
    else if(strcmp(*(arr + maxIndex), v) > 0)
        return maxIndex;
    else
        return -1;
}

int main()
{
    char a[][10] = {"abc", "bcd", "bddaaa", "ddcd", "ddd", "ddd", "ddd", "ddd", "xxx", "xxx"};
    char v[] = "zzz";
    int last = sizeof(a) / (sizeof(char) * 10);
    int index = bisearch(a, 0, last-1, v);
    printf("index of v is %d\n", index);

    return 0;
}


非常好的问题! 你问的是:“这和二分查找有什么关系?” 我们来深入解释:**为什么在最长递增子列(LIS)的 $ O(n \log n) $ 解法中要用到二分查找?它是如何起作用的?** --- ### ✅ 简短回答: > **因为我们要在一个始终保持有序数组 `tails` 中,快速找到第一个 ≥ 当前数字的位置,这个操作就是「二分查找」的经典应用场景。** --- ### ✅ 详细解释:从数据结构与算法设计角度 #### 回顾我们的优化思路 我们维护一个数组: ```python tails = [] ``` 其中 `tails[k]` 表示:长度为 `k+1` 的所有递增子列中,最小的结尾元素。 关键性质: - `tails` 数组是**严格递增**的(可以数学证明) - 所以我们可以对它使用 **二分查找** --- ### 🔍 每次处理一个新数 `x` 时,我们需要做: ```python pos = bisect.bisect_left(tails, x) ``` 这句话的意思是: > 在已排数组 `tails` 中,找到第一个位置 `pos`,使得 `tails[pos] >= x` 然后根据 `pos` 做两件事之一: | 条件 | 操作 | 含义 | |------|------|------| | `pos == len(tails)` | `tails.append(x)` | `x` 比所有尾部都大,能延长 LIS | | 否则 | `tails[pos] = x` | 用 `x` 替换掉原来那个较大的值,保持“更小的尾部” | --- ### ✅ 举个例子说明二分查找的作用 ```python nums = [10, 9, 2, 5, 3, 7] ``` 逐步构建 `tails`: | x | tails before | 查找动作 | pos | 操作 | tails after | |---|---------------|----------|-----|--------|-------------| | 10 | [] | 插入位置 0 | 0 | append &rarr; [10] | | 9 | [10] | 找第一个 ≥9 &rarr; 是 10 | 0 | 替换 &rarr; [9] | | 2 | [9] | 找第一个 ≥2 &rarr; 是 9 | 0 | 替换 &rarr; [2] | | 5 | [2] | 找第一个 ≥5 &rarr; 无 | 1 (==len) | 追加 &rarr; [2,5] | | 3 | [2,5] | 找第一个 ≥3 &rarr; 5 | 1 | 替换 &rarr; [2,3] | | 7 | [2,3] | 找第一个 ≥7 &rarr; 无 | 2 | 追加 &rarr; [2,3,7] | 👉 每一步中的“查找第一个 ≥ x 的位置”都是通过 **二分查找** 完成的! 如果使用二分查找,就得线性扫描,时间复杂度变成 $ O(n) $,总复杂度退化为 $ O(n^2) $ --- ### ✅ 为什么能用二分查找? 因为: - 我们每次插入或替换后,仍然保持 `tails` **严格递增** - 所以它是一个有序数组 &rarr; 支持二分查找 ✅ 正是因为这个单调性 + 二分查找,才能把单次操作从 $ O(n) $ 降到 $ O(\log n) $ --- ### ✅ 用标准库怎么写二分查找? 如果你能用 `bisect.bisect_left`,可以手动实现: ```python def lower_bound(arr, x): """返回一个 >= x 的元素下标""" left, right = 0, len(arr) while left < right: mid = (left + right) // 2 if arr[mid] < x: left = mid + 1 else: right = mid return left ``` 然后替换为: ```python pos = lower_bound(tails, x) ``` 这就是真正的“手写二分查找”。 --- ### ✅ 总结:二分查找在这里的角色 | 角色 | 说明 | |------|------| | 🎯 目标 | 快速定位插入/替换位置 | | 📈 前提 | `tails` 始终保持有序(递增) | | ⏱️ 效果 | 将单次操作由 $ O(n) $ 降为 $ O(\log n) $ | | 💡 核心 | 利用了“贪心策略 + 二分查找”的结合 | > 类比:就像你在字典里查单词,用一页页翻,而是直接跳到大概位置 —— 二分查找就是这种高效搜索方式。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值