关于二分查找的总结和思考

二分法题需要分析问题的二段性,最简单的是已排序递增数组中找到某值位置,那么这个问题二段性为:

该值左边的所有数据都小于该值,该数组右边的所有数据都大于该值; 整个数组被查找值分为连续两段,给定一个条件,该条件必须在其一段中满足,在另一段中不能满足,选取index通过不断在两个条件中反复横跳,数据规模会以2的次方程度下降,直到两个条件的边界,最终会落在满足条件的那一方

二分法依据满足二段性条件区域分为两套模板

右区间模板

在这里设定的条件为arr[i] >= target; 写出二分法模板代码如下:

#include <iostream>
#include <vector>
using namespace std;

int bsearch(vector<int> &arr, int target)
{
    int l = 0;
    int r = arr.size()-1;
    while (l < r) {
        int mid = l + (r - l) / 2;
        if (arr[mid] >= target) {   // 条件定义处
            r = mid;
        } else {
            l = mid + 1;
        }
    }
    return l;
}

int main()
{
    vector<int> arr = {1, 1, 3, 5, 5, 8, 9};
    int index = bsearch(arr, 10);
    cout << "index " << index << endl;
    return 0;
}
依据 arr[i] >= target 条件总共分四种情况
1 target在arr数组最大和最小的范围内,且存在在数组中

请添加图片描述

因为

如以上数组,target为5,,那么整个数组在第二个元素的左边全部都小于target;第二个元素的右边全部都都大于等于target;通过这个条件将整个数组分割成两个状态;这就是二段性。

2 target在arr数组最大和最小的范围内,但是不存在在数组中

请添加图片描述

当我们明白二分法二段性的本质之后,那么下面这些性质就跟随而来; 还是上面这个例子,如果target是7,我们要搜索7,但是7在整个数组不存在,我们预期的是找不到直接返回-1, 那这里该返回什么?

这里返回的是5,也就是8对应的数组下标。因为8是满足大于等于7的第一个数。也就是说,二分法不一定能搜索到你想查找的那个值,而只是满足你定义的那个二断性条件的边界值。

3 target小于arr数组最小值

请添加图片描述

接着来看边界情况,如果target是0, 所有的数组元素都满足条件,因此这里返回零,但是要注意的是,这个时候它不是两个条件中横跳,因为没有符合另一个条件的数据,只能不断被消减规模,直到数组边界零。当然,arr[0] >= target, 符合给定的条件,可以使用。

4 target大于 arr数组最大值

请添加图片描述

接着来看上边界,当target = 10 时,也是同样没有符合另一个条件的数据,因此index被压缩到上边界,也就是数组最后一个元素9,但是最后一个元素也是不符合预期的,arr[arr.size()-1] < target; 这个index是错误的。

总结:

模板二分法查找,只有当目标在数组内才能找到准确索引索引下标。当不存在于数组内时,我们依然可以借助二断性找到边界并进行处理。

但是需要对于3和4这种超出边界的情况需要特殊处理。

左区间模板

arr[i] >= target的属性是该判断条件满足数组右边集合,另一个二分法的模板是条件满足二分法左边集合。

#include <iostream>
#include <vector>
using namespace std;

int bsearch(vector<int> &arr, int target)
{
    int l = 0;
    int r = arr.size() - 1;
    while (l < r) {
        int mid = l + r + 1 >> 1;
        if (arr[mid] <= target) {   // 条件定义处
            l = mid;
        } else {
            r = mid - 1;
        }
    }
    return l;
}

int main()
{
    vector<int> arr = {1, 1, 3, 5, 5, 8, 9};
    int index = bsearch(arr, 5);
    cout << "index " << index << endl;
    return 0;
}

当然,依据 arr[i] <= target 条件同样分四种情况,与右属性的分析一样,这里不一一赘述。

由于这里是arr[i] <= 5的边界值,那么这里的index不再是3,而是4

请添加图片描述

查找的目标值有多个的情况分析

针对同一个target, 右区域模板满足找到target的索引最小值,左区域模板是满足target的索引最大值。

通过同时调用左区间模板和右区间模板,我们可以查找到一个重复元素的左边界和右边界。

对于二段性的界定分析

简单的二分法,一般是排序数组;而难度较高的题则不一定是有序数组,可能是满足某个规律的一组数据。因此难点更多在于二段性的划分。

更可能的是,由于给定数据太过无规律,根本没有考虑到考题是一道二分题。

比如:leetcode 540题

给你一个仅由整数组成的有序数组,其中每个元素都会出现两次,唯有一个数只会出现一次。

请你找出并返回只出现一次的那个数。

你设计的解决方案必须满足 O(log n) 时间复杂度和 O(1) 空间复杂度。

示例 1:

输入: nums = [1,1,2,3,3,4,4,8,8]
输出: 2

示例 2:

输入: nums =  [3,3,7,7,10,11,11]
输出: 10

这题的难点在于二段性的识别,在target左边,奇数下标与偶数下标相等,且相同的数偶数下标在前。

在target右边,奇数下标与偶数下标相等,且相同的数奇数下标在前。通过这种二段性,可以构造check函数。

class Solution {
public:
    bool check(vector<int>& nums, int mid) {
        int x = nums[mid];
        if (mid % 2 == 0) {  
            if (nums[mid+1] == x) { 
                return false;
            } else {
                return true;  
            }
        } else {  
            if (nums[mid-1] == x) {
                return false;
            } else {
                return true; 
            }
        }
        return false; 
    }

    int search(vector<int>& nums, int l, int r) {
        while (l < r) {
            int mid = (l + r) / 2;
            if (check(nums, mid) == true) {
                r = mid;
            } else {
                l = mid + 1;
            }
        }
        return nums[l];

    }

    int singleNonDuplicate(vector<int>& nums) {
        if (nums.size() == 1) 
            return nums[0];

        return search(nums, 0, nums.size()-1);
    }
};

二维矩阵二段性分析

二维有序矩阵一般满足从左到右递增,从上到下递增。由于这个性质,【左下角】是解决二维矩阵的关键。

锚定一个左下角值x,在x下方的矩阵所有数必定大于等于这个数;如果target < x, 说明target在x的上半区域。

当target > x时,由于每次指定的x都是【左下角】,是这一列的最大值,因此target在x的右边区域。

通过这个二段性分析,不断降低行数,增加列数,直到要搜索的target变成真正的【左下角】。

leetcode 240

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:

  • 每行的元素从左到右升序排列。
  • 每列的元素从上到下升序排列。

示例 1:

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:true

示例 2:

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20
输出:false
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值