编程语言   发布时间:2022-06-26  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了二分查找总结大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。

二分查找

二分查找是一个非常基础的算法,它的原理很简单,但是写起代码来却很容易出错,尤其是在区间的范围定义时。最近刷《代码随想录》,对二分查找有了更加全面细致的理解,写一个自己的总结。在这里非常感谢《代码随想录》对我这个菜鸡的帮助。

目录
  • 二分查找
    • 前提条件
    • 边界条件(区间定义)
    • 防止溢出
    • leetcode相关题目
      • 704. 二分查找
      • 69. Sqrt(x)
      • 367. 有效的完全平方数
      • 35. 搜索插入位置
      • 34. 在排序数组中查找元素的第一个和最后一个位置

前提条件

  1. 使用二分查找时数组必须是有序
  2. 数组中没有重复元素,否则返回下标可能不一致

边界条件(区间定义)

二分查找的边界定义有两种,这里如果没有理解透彻的话,很容易写出错误的代码

要根据查找区间的定义来进行边界处理

边界定义的不同直接影响循环条件right 的更新

  1. 左闭右闭,即 [left, right] ,也就是 right 下标包含在我们要查找的数组中

    • 此时循环条件为 while (left <= right) ,因为 left == right ,即 [left, left] 是有意义的

    • 更新 right 时,right = mid - 1,因为 mid 所在下标已经查找过, 要查找 [left, mid - 1]的范围

  2. 左闭右开,即 [left, right), right 下标不包含在我们要查找的数组中

    • 此时循环条件为 while (left < right) ,因为left == right ,即 [left, left)是没有意义的

    • 更新 right 时,right = mid,因为 要查找 [left, mid) 的范围, 这个范围不包括 mid 下标

更新 left 时 ,都是 left = mid + 1

防止溢出

我们看网上很多的代码计算 mid 时

不是 mid = (left + right)/ 2 ,

而是 mid = left + (right - left)/2

两者是等价的,但是前者当 left 和 right 数值都很大时,容易溢出,后者则不会

leetcode相关题目

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

提示:

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


这是个二分查找的基本问题,给出两种写法,后面的题目默认只给出左闭右闭的写法

  1. 左闭右闭写法

    class Solution {
    public:
        //左闭右闭写法
        int search(vector<int>& nums, int target) {
            int left = 0, right = nums.size() - 1;  //左闭右闭区间
            while (left <= right) {                 //条件是 <=
                int mid = left + (right-left)/2;    //防止溢出
                if (nums[mid] == target) {          //找到直接返回下标
                    return mid;
                } else if (nums[mid] > target) {    //此时更新区间为左边
                    right = mid - 1;
                }
                else {                              //此时更新区间为右边
                    left = mid + 1;
                }
            }
            return -1;                              //没找到返回-1
        }
    };
    
  2. 左闭右开写法

    class Solution {
    public:
        // 左闭右开写法
        int search(vector<int>& nums, int target) {
            int left = 0, right = nums.size();   //区别1:左闭右开区间
            while (left < right) {               //区别2:判断条件        
                int mid = left + (right-left)/2;    
                if (nums[mid] == target) {          
                    return mid;
                } else if (nums[mid] > target) {    
                    right = mid;                //区别3:right处理
                }
                else {                              
                    left = mid + 1;
                }
            }
            return -1;                              
        }
    };
    

69. Sqrt(x)

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 1:

输入:x = 4 输出:2 示例 2:

输入:x = 8 输出:2 解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

提示:

0 <= x <= 231 - 1


这道题目规定了不能使用指数函数和算符,我们想到通过遍历整数的平方,整数是有序的,可用二分查找

这道题目要写出没有问题的代码要注意这几点

  • left 要初始化为 1 而非 0 或者特殊处理输入为0或1的情况
  • mid * mid == x会溢出, 要用 mid == x / mid (mid 和 target都是整数)
  • 如果找到 mid ==x / mid 时返回mid,没找到就返回最大的平方小于 x 的整数 ,即 right。
class Solution {
public:
    int mySqrt(int x) {
        // left 一定需要是从 1 开始,原因是当输入是 x=1 时,计算出的mid=0,计算 x/mid 时会报错。
	    // 另外,当输入是 大于等于 1 时也不影响结果计算,当输入是 0 时,输出是 right(0) ,所以也是合理的。
        int left = 1, right = x;
        //左闭右闭区间
        while (left <= right) {
            int mid = left + (right - left) / 2; 
            if (mid > x / mid) {
                right = mid - 1;
            }
            else if (mid < x/mid) {
                left = mid + 1;
            }
            else if (mid == x/mid) {
                return mid;
            }
        }
        // 没找到就返回最大的一个平法小于x的值
        return right;
    }
};

367. 有效的完全平方数

给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。

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

**

示例 1:

输入:num = 16 输出:true 示例 2:

输入:num = 14 输出:false

提示:

1 <= num <= 2^31 - 1


这题与上一题类似,只不过要判断余数是否为零

class Solution {
public:
    bool isPerfectSquare(int x) {
        int left = 1,right = x;
        while (left <= right) {
            int mid = left + (right - left)/2;
            if (mid == x/mid) {
                if (x % mid == 0) {				//是否整除
                    return true;
                } 
                return false;
            } else if (mid > x/mid) {
                right = mid - 1;
            } else if (mid < x/mid) {
                left = mid + 1;
            }
        }
        return false;
    }
};

35. 搜索插入位置

问题描述:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

示例 1:

*输入: nums = [1,3,5,6], target = 5 输出: 2

示例 2:

输入: nums = [1,3,5,6], target = 2 输出: 1

示例 3:

输入: nums = [1,3,5,6], target = 7 输出: 4

示例 4:

输入: nums = [1,3,5,6], target = 0 输出: 0

示例 5:

输入: nums = [1], target = 0 输出: 0


这道题和上一题区别在于,如果没有找到返回插入位置,而不是返回-1。

没有找到最后要返回什么呢?我们考虑以下几种情况

  • 查找的元素比数组中所有元素都小,最后 left = 0 和 right = -1 ,我门返回0
  • 查找的元素比数组中所有元素都大, 最后left = n + 1 (n为元素个数), right = n
  • 查找的元素大小位于数组区级,最后 left = right + 1

所以返回left

class Solution {
public:
    //左闭右闭区间
    int searchInsert(vector<int>& nums, int target) {
       int left = 0, right = nums.size() - 1;
       while (left <= right) {
           int mid = left + (right-left)/2;
           if (nums[mid] == target) {
               return mid;
           } else if (nums[mid] > target) {
               right = mid - 1;
           } else {
               left = mid + 1;
           }
       }
       // 结束时 left = right + 1
       return left;
    }
};

34. 在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

进阶:

你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8 输出:[3,4] 示例 2:

输入:nums = [5,7,7,8,8,10], target = 6 输出:[-1,-1] 示例 3:

输入:nums = [], target = 0 输出:[-1,-1]

提示:

0 <= nums.length <= 105 -109 <= nums[i] <= 109 nums 是一个非递减数组 -109 <= target <= 109


  • 通过二分查找先找到目标值,然后前后遍历,找到起始位置和结束位置,时间复杂度为nlog(n)

    class Solution {
    public:
        vector<int> searchRange(vector<int>& nums, int target) {
            int right = nums.size() - 1;
            vector<int> res(2, -1);  //初始化起始和结束位置,-1为没找到
            int left = 0;
            //左闭右闭区间
            while (left <= right) {
                int mid = left + (right - left)/2;
                if (nums[mid] == target) {   	
                    int i = mid, j = mid;
                    while (i >= 0 && nums[i] == target) {//找到目标值之后,循环遍历寻找起始位置
                        i--;
                    }
                    while (j < nums.size() && nums[j] == target) {//循环遍历寻找结束位置
                        j++;
                    }
                    res[0] = i + 1;
                    res[1] = j - 1;
    
                    return res;
                }
                else if (nums[mid] > target) {
                    right = mid - 1;
                }
                else {
                    left = mid + 1;
                }
            }
            return res;
        }
    };
    
  • 直接通过二分查找找到起始位置和结束位置,时间复杂度为log(n)

    
    

大佬总结

以上是大佬教程为你收集整理的二分查找总结全部内容,希望文章能够帮你解决二分查找总结所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。
标签: