「力扣」第 704 题:二分查找(简单)


「力扣」第 704 题:二分查找(简单)

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例 1:

输入: nums = [-1, 0, 3, 5, 9, 12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

输入: nums = [-1, 0, 3, 5, 9, 12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

提示:

1、你可以假设 nums 中的所有元素是不重复的。
2、n 将在 [1, 10000] 之间。
3、nums 的每个元素都将在 [-9999, 9999] 之间。

思路

根据看到的中间元素的数值,想清楚下一次搜索的区间是什么,进而设置 left 或者 right 的值。

方法一:在循环的过程中判断目标元素是否存在

这个版本的二分查找是教科书里经常介绍的写法。

  • 在循环中做判断,每一轮通过目标元素与中间元素的大小将搜索区间分为 3 个部分;
  • 有非递归写法和递归写法。
  • 优点:语义非常清晰,不容易出错;

参考代码 1

Java 代码:(循环写法)

public class Solution {

    public int search(int[] nums, int target) {
        int len = nums.length;
        if (len == 0) {
            return -1;
        }
        // 在 [left, right] 区间里查找 target
        int left = 0;
        int right = len - 1;
        while (left <= right) {

            int mid = (left + right) >>> 1;

            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] > target) {
                // 下一轮搜索区间:[left, mid - 1]
                right = mid - 1;
            } else {
                // 此时:nums[mid] < target
                // 下一轮搜索区间:[mid + 1, right]
                left = mid + 1;
            }
        }
        // 走到这里,就可以判定输入数组里不存在目标元素,返回 -1
        return -1;
    }
}

Java 代码:(递归写法)

public class Solution {

    public int search(int[] nums, int target) {
        int len = nums.length;
        if (len == 0) {
            return -1;
        }
        return binarySearch(nums, target, 0, len - 1);
    }

    private static int binarySearch(int[] arr, int target, int left, int right) {
        // 先处理递归到底的情况
        if (left > right) {
            // 不能形成区间,返回 -1 表示没有找到
            return -1;
        }
        int mid = (left + right) >>> 1;
        if (target == arr[mid]) {
            // 找到了,就将目标元素的索引返回
            return mid;
        } else if (target < arr[mid]) {
            // 既然是有序数组,目标元素的值比中间元素还要小,就应该在中间元素的左边去找
            return binarySearch(arr, target, left, mid - 1);
        } else {
            // 既然是有序数组,目标元素的值比中间元素还要大,就应该在中间元素的右边去找
            return binarySearch(arr, target, mid + 1, right);
        }
    }
}

复杂度分析

  • 时间复杂度:$O(\log N)$,这里 $N$ 是数组的元素个数,每次排除当前候选区间一半以上的元素,因此是对数级别的时间复杂度。
  • 空间复杂度:$O(1)$,只使用了常数个的临时变量。

方法二:在循环结束以后判断目标元素是否存在

特点:在循环结束以后判断目标元素是否存在。在循环中,分支只有两个判断更少。

缺点:如果这个模板写法掌握得不好,容易出错。

Java 代码:

public class Solution {

    public int search(int[] nums, int target) {
        int len = nums.length;
        int left = 0;
        int right = len - 1;
        while (left < right) {

            int mid = (left + right) >>> 1;

            if (nums[mid] < target) {
                // 下一轮搜索区间是:[mid + 1, right]
                left = mid + 1;
            } else {
                // 此时 nums[mid] >= target,
                // mid 的右边一定不存在 target,下一轮搜索区间是:[left, mid]
                right = mid;
            }
        }
        // 不要忘了单独做判断
        if (nums[left] == target) {
            return left;
        }
        return -1;
    }
}

Java 代码:

public class Solution {

    public int search(int[] nums, int target) {
        int len = nums.length;

        int left = 0;
        int right = len - 1;

        while (left < right) {
            // 注意:根据分支逻辑,需要在取中间数的时候,向上取整
            int mid = (left + right + 1) >>> 1;

            if (nums[mid] > target) {
                // 下一轮搜索区间是:[left, mid - 1]
                right = mid - 1;
            } else {
                // 此时 nums[mid] <= target
                // mid 的左边一定不等于目标元素
                // 下一轮搜索区间是:[mid, right]
                left = mid;
            }
        }
        // 不要忘了单独做判断
        if (nums[left] == target) {
            return left;
        }
        return -1;
    }
}

注意

看到 left = mid; ,一定要记得将取中间数的行为上取整,即 int mid = (left + right + 1) >>> 1;

(本节完)


文章作者: liweiwei1419
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 liweiwei1419 !
评论
 上一篇
「数组」专题 1:简单问题汇总 「数组」专题 1:简单问题汇总
「数组」专题 1:简单问题汇总「力扣」第 215 题:数组中的第 K 个最大元素1、使用优先队列完成; 2、使用快速排序完成。 Java 代码: public class Solution { /** * 给定任意一个数
下一篇 
「力扣」第 658 题:找到 K 个最接近的元素 「力扣」第 658 题:找到 K 个最接近的元素
「力扣」第 658 题:找到 K 个最接近的元素题解地址:排除法(双指针) + 二分法(Python 代码、Java 代码)。 说明:文本首发在力扣的题解版块,更新也会在第 1 时间在上面的网站中更新,这篇文章只是上面的文章的一个快照,您可
  目录