704.二分查找

324 阅读2分钟

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

提示:

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

思路


因为是无重复元素的有序序列(升序),故采用二分查找算法去实现是时间复杂度较好的一种方法,其中无重复元素的目的是为了保证每次的返回的下标都是唯一的。

关键点回忆


  • 书写二分法代码的一定要注意你所定义的搜索区间的边界(主流的有左闭右闭,左闭右开),因为不同的边界所对应的代码的判断条件会有细微的差别。

  • 在二分查找的过程中一定要保持每次缩小后的查找范围保持 "不变量",即每次缩小后的查找范围都满足你所定义的搜素区间。(保证搜索区间的合法性)

  • 二分查找一般while循环判段的条件是left < right 或者是left <= right,会发现始终保持左边界小于右边界,是因为在最坏的时间复杂度的情况下,我们会循环查找到序列中最后一个未被判断的元素时,这是left = right = mid,所以这时若“if”判断后仍未找到目标值,在更新边界值后,不论是right = mid-1,还是left = mid +1,我们发现都是left > right,并且这时所有需要判断的元素都被判断了(即最坏时间复杂度的情况下),所以while循环的判断条件是left < right 或者是left <= right。

c++代码实现方法一

#include <iostream>
#include <vector> //引入vector容器的头文件

using namespace std;

//法一:定义合法的搜索区间为!!!~~左闭右闭时~~
class Solution {
  public:
    //对升序序列进行目标值搜索
    int search_target(vector<int>& nums, int target) {
        int left = 0;                //左边界
        int right = nums.size() - 1; //!!!右边界,保证左闭右闭
        // !!!left == right时,区间[left , right]依然合法,并且这时候mid = left = right
        while (left <= right) {
            //!!!防止int型变量溢出,等同于(left+right)/2,即左边界位置加上左右边界距离的一半就是mid所在的数组下标
            int mid = left + (right - left) / 2; //搜索区间的中间位置

            //不断缩小搜索区间
            //当中间值大于目标中的时候,代表[mid,right]区间内的元素均大于target,故更新左区间的右边界
            if (nums[mid] > target)
                right = mid - 1; //更新区间为[left , mid-1]

            //当中间值小于目标中的时候,代表[left,mid]区间内的元素均小于target,故更新右区间的左边界
            else if (nums[mid] < target)
                left = mid + 1; //更新区间为[mid + 1 , right]

            //目标值等于中间值,直接返回下标
            else
                return mid;
        }
        //未找到目标值
        return -1;
    }
};

int main() {
    //测试一下
    //声明一个vector容器
    vector<int> nums = {2, 3, 5, 6, 21, 23, 29};
    Solution s1;
    int pos = s1.search_target(nums, 6);
    cout << pos;
    return 0;
}

c++代码实现方法二

//法二:定义合法的搜索区间为!!!~~左闭右开~~
class Solution {
  public:
    //对升序序列进行目标值搜索
    int search_target(vector<int>& nums, int target) {
        int left = 0; //左边界
        int right = nums.size(); //!!!右边界,保证左闭右开,不包括right这个边界
        //!!!left == right 的时候[left , right]不满足左闭右开,是个非法的区间
        while (left < right) {
            //  !!!防止int型变量溢出,等同于(left+right)/2,即左边界位置加上左右边界距离的一半就是mid所在的数组下标  
            //搜索区间的中间位置,>>1是算数右移一位,相当于除二,但是比直接除2速度要快
            int mid =left +((right - left) >>1);

            // 不断缩小搜索区间
            //当中间值大于目标值的时候,代表[mid,,right)区间内的元素均大于目标值,故更新左区间的右边界
            if (nums[mid] > target)
                right = mid; //更新区间为[left, mid),其实就等价于[left, mid-1]

            //当中间值小于目标值的时候,代表[left,mid]区间内的元素均小于目标值,故更新右区间的左边界
            else if (nums[mid] < target)
                left = mid + 1; //更新区间为[mid + 1 , right)

            //目标值等于中间值,直接返回下标
            else
                return mid;
        }

        //未找到目标值
        return -1;
    };
};

int main() {
    //测试一下
    //声明一个vector容器
    vector<int> nums = {2, 3, 5, 6, 21, 23, 29};
    Solution s1;
    int pos = s1.search_target(nums, 21);
    cout << pos;
    return 0;
}