二分查找

241 阅读3分钟

参考闫大佬的二分模板教程

二分主要用于降低时间复杂度,可以将 O(n) 复杂度降低为 O(logn)

二分模板

版本一:

int search_1(int l, int r) {
    while(l < r) {
        int mid = (l + r) >> 1;
        if (判断条件)  r = mid;
        else l = mid + 1;
    } 
    return l;
}

版本二:

int search_2(int l, int r) {
    while(l < r) {
        int mid = (l + r + 1) >> 1;
        if (判断条件)  l = mid;
        else r = mid - 1;
    } 
    return l;
}

总结:

  • 这里 mid + 1 或 mid - 1 是将 mid 归为 另外一半,mid 不可能同时处于两边
  • 版本一和版本二主要区别是:版本一处理结果是得到目的值最小情况,版本二是处理结果是得到目的值最大的情况:例如给定排序数组 [5,7,8,8,8,10] 获得 target = 8 在数组中的第一个和最后一个位置的下标,第一个模板可以返回最小下标即 2,第二个模板返回最大下标 4
  • 注意版本二中 int mid = (l + r + 1) >> 1; 是为了避免死循环

例题一:

有效的完全平方数:给定一个正整数 num,编写一个函数,如果 num 是一个完全平方数,则返回 True,否则返回 False。

说明:不要使用任何内置的库函数,如  sqrt。

class Solution {
    public boolean isPerfectSquare(int num) {
       int l = 1, r = num / 2;
       while (l < r) {
           int mid = (l + r) >> 1;
           if (mid  >= num / mid) r = mid;
           else l = mid + 1; 
       }
       return l*l == num;
    }
}

例题二:

在排序数组中查找元素的第一个和最后一个位置 : 给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。 你的算法时间复杂度必须是 O(log n) 级别。 如果数组中不存在目标值,返回 [-1, -1]。

从这个题目能够看出版本一和版本二的主要区别

class Solution {
    public int[] searchRange(int[] nums, int target) {
        if(nums == null || nums.length == 0 || target < nums[0] || target > nums[nums.length - 1]) return new int[]{-1, -1};
        
        // 二分查找优化算法
        int l = 0, r = nums.length - 1;
        int start;
        // 大于等于最小的
        while(l < r) {
            int mid = (l + r) >> 1;
            if (nums[mid] >= target) r = mid;
            else l = mid + 1;
        }
        // 二分查找一次,如果找不到说明这个数组中没有该元素
        if (nums[l] != target) return new int[]{-1,-1};
        else start = l;

        l = 0;
        r = nums.length - 1;
        // 小于等于最大的
        while(l < r) {
            int mid = (l + r + 1) >> 1;
            if (nums[mid] <= target) l = mid;
            else r = mid - 1;
        }
        return new int[]{start, l};
    }
}

例题三:

寻找峰值:峰值元素是指其值大于左右相邻值的元素。给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。 数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。 你可以假设 nums[-1] = nums[n] = -∞。

主要解题思想:如果给定一个 nums[mid] 不为峰值的话,那么必然有峰值 nums[mid] 中(保证这个条件成立的是数组的边界是 -∞)

class Solution {
    public int findPeakElement(int[] nums) {
        if (nums.length == 1 || nums[0] > nums[1]) return 0;
        
        int l = 0, r = nums.length - 1;
        while (l < r) {
            int mid = (l + r + 1) >> 1;
            if (nums[mid - 1] < nums[mid]) l = mid;
            else r = mid - 1;
        }
        return l;
    }
}