二分查找

151 阅读3分钟

一、二分查找

二分查找也是一种在数组中查找数据的算法。它只能查找已经排好序的数据。二分查找通过比较数组中间的数据与目标数据的大小,可以得知目标数据是在数组的左边还是右边。因此,比较一次就可以把查找范围缩小一半。重复执行该操作就可以找到目标数据,或得出目标数据不存在的结论。

例如:查找数字6

首先找到数组中间的数字,此处为5。

将5和要查找的数字6进行比较。

把不需要的数字移出查找范围。

在剩下的数组中找到中间的数字,此处为7。 

比较7和6

把不需要的数字移出查找范围。

在剩下的数组中找到中间的数字,此处为6。

6=6,成功找到目标数字

解说:

**二分查找利用已排好序的数组,每一次查找都可以将查找范围减半。查找范围内只剩一个数据时查找结束。
**

时间复杂度:O(logn).

二、二分法的难点

1. 边界判断

二分法查找代码大致如下

function search (nums, target) {
    var begin = 0;
    var right = ???;
    var middle = 0;
    while(???) {
        middle = Math.floor((left + right)/2);
        if(nums[middle] === target) {}
        else if(nums[middle] > target){}
        else {}
    }
    return ???;
}

基本的框架已经出来了,难点在于边界条件不清楚。

边界问题一般有两种写法:

第一种写法:我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right]

区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:

  • while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=

  • if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1

    function search ( nums, target ) { ver left = 0; var right = nums.length - 1; var middle = 0; while(left <= middle) { // 因为left === right是有意义的,所以使用<= middle = Math.floor((left + right) / 2); if (nums[middle] === target) { return middle; } else if (nums[middle] > target) { right = middle - 1; // 因为是必区间,搜索范围为[left,middle-1] } else { left = middle + 1; // 因为是开区间,搜索范围为[middle + 1, right] } } return -1; }

第二种写法:定义了target在一个左闭右开的区间里,也就是 [left, right)

有如下两点:

  • while (left < right) 要使用 < ,因为left == right是有意义的,所以使用 <

  • if (nums[middle] > target) right 要赋值为 middle,因为当前这个nums[middle]一定不是target,那么在左侧区间查找,因为区间是左闭右开的区间,因此right = midle,那么接下来要查找的左区间结束下标位置就是 middlle

    function search (nums,target) { var left = 0; var right = nums.length; var middle = 0; while(left < right) { middle = Math.floor((left + right) / 2); if(nums[middle] === target) { return middle; } else if (nums[middle] > target) { right = middle;// 因为是左闭右开区间,搜索范围是[left,middle) } else { left = middle+ 1;// 因为是左闭右开区间,搜索范围是[middle+1,right) } } return -1; }

2. 数据重复

统计一个数字在排序数组中出现的次数。

思路:找到这个数字在数组中的最大索引(右边界)rI,接着找这个数字在数组中的最小索引(左边界)lI,出现的次数为rI - II

1. 查找最大索引(右边界)bI

function serchRight(nums, target) {
    let left = 0;
    let right = nums.length -1;
    let middle = 0;
    while(left <= right) {
        middle = Math.floor((left + right) / 2);
        if(nums[middle] <= target) { // 只要中间值 <= target,那么最大索引在右区间
            left = middle + 1;
        } else { // 中间值 > target, 那么最大索引一定在左区间
            right = middle - 1; 
        }
    }
    return right// 也可以写成 left - 1
}

2. 查找完最大索引,需要判断这个数字是否在数组中

如果在数组中,则继续查找最小索引;如果不存在,直接返回0,没有查找最小索引的必要

function search(nums, target) {
    var rI = searchRight(nums,target);
    if (nums[rI] !== target) return 0;
    //  查找最小左索引
}

3. 查找最小左索引

function searchLeft(nums, target) {
    let left = 0;
    let right = nums.length - 1;
    let middle = 0;
    while(left <= right) {
        middle = Math.floor((left + right)/2);
        if(nums[middle] >= target) {
            right = middle - 1; 
        } else {
            left = middle + 1;
        }
    }
    return left;
}

4. 计算该数字出现的次数

function search(nums,target) {
    let rI = searchRight(nums,target);
    if (nums[rI] !== target) return 0;
    let lI = searchLeft(nums,target);
    return rI - lI + 1; 
}