704. 二分查找

188 阅读3分钟

[704,二分查找]

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。

题目描述

给定一个 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

思路

经过昨天和前天的两道二分查找的算法题,今天使用一道典型的简单的二分查找来对可以使用二分查找的题进行一个总结。首先,需要明确,什么样的题可以使用二分查找。那就是在有序的数组中,找到特定的一个值。今天的这道题很明显了,现成的数组已经在这里了。那么,前两天的那两道题为什么可以呢?经过分析,可以发现,与这道题相比,那两道题只是多了一层映射(运载量映射到天数,速度映射到小时数),且映射出来的结果也是有序的,那么就可以使用二分查找了。

明确使用二分查找之后,下来就是明确区间的定义了。这里涉及到左右区间left,right 一开始的取值。以及是while(left<=right)还是 while(left<right),需不需要+1或者-1。以下通过代码来具体解释。

代码实现

一般来说,left和right 的取值有俩种取法,即[left,right]和[left,right).

左闭右闭:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
        while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
            int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
            if (nums[middle] > target) {
                right = middle - 1; // target 在左区间,所以[left, middle - 1]
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,所以[middle + 1, right]
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};

左闭右开:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
        while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
            int middle = left + ((right - left) >> 1);
            if (nums[middle] > target) {
                right = middle; // target 在左区间,在[left, middle)中
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,在[middle + 1, right)中
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};

对比两种写法,可以发现以下不同,右边如果是闭的,那么,right=middle-1,这是因为,经过判断,此时middle的值是大于target的,进入了无效区间,需要排除出下一轮的查找区间,所以需要right-1。而右边如果是开的,本来right就不在查找区间内,所以不需要right-1。而left始终是闭的,所以需要left+1。另外while之中需不需要=,取决于当left==right时,区间是否为空,如果是左闭右闭的,那么区间不为为空,需要=,反之,则需要。总结一下,在决定好区间的取值后,就需要保证在每一次查找中,需要保证区间是有效的,进而判断while需不需要=,用不用+-1。

总结

上述内容总结了二分查找的适用条件,以及在确定查找区间需要注意的点,只是涉及到了数组中,没有重复数字的情况,如果有的话要怎么办?如果我要重复数字中第一个出现的下标,或者是最后一个出现的下标,那该如何编写代码。 请期待明天的进一步分析。