编程导航算法通过村第九关 | 二分查找

103 阅读3分钟

二分查找与分治

在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

这个技巧是很多高效算法的基础,如二分搜索、排序算法(快速排序,归并排序)等等……

任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。例如,对于n个元素的排序问题,当n=1时,不需任何计算。n=2时,只要作一次比较即可排好序。n=3时只要作3次比较即可,…。而当n较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。

二分查找就是将中间结果与目标进行比较,一次去掉一半,因此二分查找可以说是最简单、最典型的分治了。

二分查找,不管是循环还是递归方式,我觉得应该达到写到闭着眼睛,一分钟就能写出来的地步。

这里再补充一个问题, 分治和递归是一回事吗?很显然不是。这是两种完全不同的思想 ,二分查找是分支思想,我们可以使用递归或者循环的方式来做。而很多递归问题也不一定是分治的,因此两个完全不是一回事。

. - 力扣(LeetCode)

循环的方式

在计算机中,除的效率非常低,一般可以使用移位来代替

public int search(int[] nums, int target) {
    int low = 0;
    int high = nums.length - 1;
    while (low <= high) {
        int mid = low + ((high - low) >> 1);
        if (nums[mid] == target) {
            return mid;
        }else if (nums[mid] > target) {
            // 由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
            high = mid - 1;
        }else {
            // 由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
            low = mid + 1;
        }
    }
    return -1;
}

递归的方式

public  int search2(int[] array, int low, int high, int target) {
    //递归终止条件
    if(low <= high){
        int mid = low + ((high - low) >> 1);
        if(array[mid] == target){
            return mid  ;  // 返回目标值的位置,从1开始
        }else if(array[mid] > target){
            // 由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
            return search2(array, low, mid-1, target);
        }else{
            // 由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
            return search2(array, mid+1, high, target);
        }
    }
    return -1;   //表示没有搜索到
}

元素中有重复的二分查找

这里的关键是找到目标结果之后不是返回而是继续向左侧移动。第一种,也是最简单的方式,找到相等位置向左使用线性查找,直到找到相应的位置。

public static int search(int[] nums, int target) {
    if (nums == null || nums.length == 0)
        return -1;
    int left = 0;
    int right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1;
        } else {
            //找到之后,往左边找
            while (mid != 0 && nums[mid] == target)
            mid--;
            if (mid == 0 && nums[mid] == target) {
                return mid;
            }    
            return mid + 1;
        }
    }
    return -1;
}