二分查找总结

178 阅读3分钟

二分的主要细节在于左右区间的选择。 如leetcode704.

[704] Binary Search

原题链接

var search = function(nums, target) {
    let start = 0;
    let end = nums.length - 1;
    while(start <= end){
        let mid = start + Math.floor((end - start)/2);
        let midVal = nums[mid];
        if(target === midVal) return mid;
        if(target < midVal){
            end = mid - 1;
        }else{
            start = mid + 1;
        };
    };
    return -1;
};

主要的要点在于要清楚查找的区间。这里是一个前闭合后闭合。

左侧边界查找

除了基本的排序不重复数组中查找值,还有一种在排序重复数组中查找值的左右边界。 如[1,2,3,3,3,3,4,5]数组,给定值3,求查找出该数组中第一次出现3这个值的位置。该种查找叫做左侧边界查找。这种做法可以选择二分,也可以选择遍历。遍历的时间复杂度过高,要O(n)。

var search = function(nums,target){
    let left = 0;
    let right = nums.length;
    //采用左闭右开的区间
    while(left < right){
        let mid = left + Math.floor((right - left)/2);
        if(nums[mid] === target){//如果相等,收缩右边界
            right = mid;
        }else if(nums[mid] < target){//如果target > nums[mid],左边界+1
          left = mid + 1;
        }else if(nums[mid] > target){ //如果target < nums[mid],收缩右边界
          right = mid;
        };
    }
    return left;
}

右侧边界查找

同样是采用左闭右开,别的地方都容易推理,主要是返回值与之前不同。因为是左闭右开区间,并且整体是左区间在收缩,因此要找的值会在left和right的左侧,因此要减1.

var right_bound = function(nums, target) {
    if(nums.length === 0) return -1;
    let left = 0,right = nums.length;
    //采用左闭右开的区间
    while(left < right){
        let mid = Math.floor((left + right) / 2);
        if(nums[mid] === target){
            left = mid + 1; //如果相等,收缩左边界
        }else if(nums[mid] < target){
            left = mid + 1; //target大于nums[mid],收缩左边界
        }else if(nums[mid] > target){
            right = mid; //target小于nums[mid],收缩右边界
        }
    };
    return left - 1;//返回右边界
}

变种:搜索旋转数组中的某个值

leetcode的原题:搜索旋转排序数组

本质上仍然是基础的二分查找,只是旋转数组中,存在一个大段和小段的问题,,因此多了一步中点值位于哪一块的问题。

var search = function(nums, target) {
    let start = 0; let end = nums.length - 1;
    while(start <= end){ //基础的二分查找
        let mid = start + Math.floor((end - start)/2);
        if(nums[mid] === target) return mid; //查找到结果返回
        if(nums[start] <=nums[mid]){ //中点值位于大段
            if(target >= nums[start] && target < nums[mid]){
                end = mid - 1;
            }else {
                start = mid + 1;
            }
        }
        else if(nums[start] > nums[mid]){ //中点值位于小段
            if(target > nums[mid] && target <= nums[end]){
                start = mid + 1;
            }else {
                end = mid - 1;
            }
        }
    }
    return -1;
};

变种:搜索旋转数组中的某个值II

leetcode的原题: 搜索旋转排序数组II

本题是上一题的变种,多了一个重复数字,因此在判断大小段的时候,不能使用<= 。对于==的情况要特殊另外处理。

var search = function(nums, target) {
    let start = 0 ;
    let end = nums.length - 1;
    while(start <= end){
        let mid = start + Math.floor((end - start)/2);
        if(target === nums[mid]) return true;
        if(nums[start] < nums[mid]){
            if(target >= nums[start] && target < nums[mid]){
                end = mid - 1;
            }else{
                start = mid + 1;
            }
        }else if (nums[start] > nums[mid]){
            if(target> nums[mid] && target <= nums[end] ){
                start = mid + 1;
            }else {
                end = mid - 1;
            }
        }else if(nums[start] === nums[mid]){ //如果等于,就往里缩,因为要找的值必定只能在区间内部。
            start++;
        };
    };
    return false;
};