二分查找

首页 / 算法笔记 / 正文

二分查找

基础思想

在一个大的区间内中的一个小区间进行查找,如果小区间中没有则排除这个区间再进行查找,不断的排除小区间,直到找到目标为止。二分查找的关键所在就是排除区间的条件。

例题

剑指Offer11

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

思路:设left和right分别指向数组的两端,设temp = (right + left)/2,分别有以下三种情况

num[temp] < num[left] 则目标数字一定在区间[left, temp]中

num[temp] > num[left] 则目标数字一定在区间[temp, right]中

num[temp] = num[left] 无法判断目标数字在哪个区间中,需要缩小判断区间,但是这种情况下,一定会有左区间或者右区间中数字全部相等,或两个区间都满足数字全部相等,可以放弃二分查找,直接线性查找

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int right = numbers.size() - 1;
        int left = 0;
        while(left < right){
            int temp = (right + left) / 2;
            if(numbers[temp] > numbers[right]){
                left = temp + 1;
            }
            else if(numbers[temp] < numbers[right]){
                right = temp;
            }
            else{
                int x = left;
                for(int i = left; i < right; i++){
                    if(numbers[i] < numbers[x]){
                        x = i;
                    }
                }
                return  numbers[x];
            }
        }
        return numbers[left];
    }
};

剑指Offer53-1

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

输入: nums = [5,7,7,8,8,10], target = 8 输出: 2

思路:首先我们还是要判断二分循环的条件,同样的right,left,m

nums[m] < target,则target在右区间

nums[m] > target,则target在左区间

nums[m] = target,则target的右边界在右区间中,左边界在左区间中,又因为目标数字可能不止出现一次,所以我们得找到它的左右边界

寻找左边界:执行left = m - 1 寻找右边界:执行right = m + 1

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int i = 0, j = nums.size() - 1;
        while(i <= j){
            int m = i + (j - i) / 2;
            if(nums[m] <= target){
                i = m + 1;
            }else{
                j = m -1;
            }
        }
        int right = i;
        if(j >= 0 && nums[j] != target){
            return 0;
        }
        i = 0,j = nums.size() - 1;
        while(i <= j){
            int m = i + (j - i) / 2;
            if(nums[m] < target){
                i = m + 1;
            }else{
                j = m - 1;
            }
        }
        int left = j;
        return right - left - 1;
    }
};

优化:寻找边界可以抽象为一个函数用来寻找任意数字的边界,所以我们只需要寻找target的右边界和target - 1的右边界然后相减即可。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        return searchBoundary(nums,target) - searchBoundary(nums,target - 1);
    }
private:
    int searchBoundary(vector<int>& nums, int target){
        int i = 0, j = nums.size() - 1;
        while(i <= j){
            int m = i + (j - i) / 2;
            if(nums[m] <= target){
                i = m + 1;
            }else{
                j = m - 1;
            }
        }
        return i;
    }    
};

剑指Offer53-2

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

输入: [0,1,2,3,4,5,6,7,9]
输出: 8

思路:首先我们还是要判断二分循环的条件,同样的right,left,m,通过这个数组我们可以确定,如果是一个正常的递增数组,数组下标指和所指向的值是相对应的。如果不相对应,那么缺失的数字一定在m的右区间中。因为如果缺失数字在m的左区间中,则m绝对和它的下标值相对应。判断条件如下

当nums[m] == m 则缺失数字在左区间

当nums[m] != m 则缺失数字在右区间

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int left = 0, rigth = nums.size() - 1;
        while(left <= rigth){
            int m = (left + rigth) / 2;
            if(nums[m] == m){
                left = m + 1;
            }else{
                rigth = m - 1;
            }
        }
        return left;
    }
};

总结

二分查找主要的解决方法就是确定判断的条件,但是不同的题目的判断条件具有很大的差异性,所以在解题的过程中需要仔细的验证条件的合理性

无标签
评论区
头像
文章目录