算法 || 二分查找

目录

二分查找

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

搜索插入位置 


 

一个数组经过划分后具有二段性的都可以用二分查找

二分查找

704. 二分查找 - 力扣(LeetCode)

暴力解法:直接遍历数组,找到 target 便返回下标,数组都遍历完了仍找不到,则返回 -1,时间复杂度为 O ( n )(最坏的情况:如果数组中不存在 target,则需要遍历整个数组)。

注意到题目中提供的数组是升序的,即数组从左往右是递增的。我们在数组中随意找个下标,设为 i,

1、如果nums[ i ] > target ,由于数组升序,说明 i 右边的数(即比 nums[ i ] 大的数)一定也大于 target,所以 target 应该落在 i 的左边,我们就不必遍历 i 右边的数了

2、同理,如果nums[ i ] < target ,由于数组升序,说明 i 左边的数(即比 nums[ i ] 小的数)一定也小于 target,所以 target 应该落在 i 的右边,我们就不必遍历 i 左边的数了

3、如果 nums[ i ] == target,那么 i 就是我们想要的返回值

 于是衍生出二分查找, 定义 left、right、mid,

1、如果 nums[ mid ] > target,mid 左边的数不必遍历了,所以 right = mid - 1

2、如果 nums[ mid ] < target,mid 右边的数不必遍历了,所以 left = mid + 1

3、如果 nums[ mid ] == target,说明找到了,返回 mid

4、如果 left > right,说明数组中不存在 target ,返回 -1

要注意 mid 的计算,防止溢出!!

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left=0;
        int right=nums.size()-1;
        while(left<=right)
        {
            //int mid=(left+right)/2;
            int mid=left+(right-left)/2;//防止 left+right 溢出
            if(nums[mid]>target) right=mid-1;
            else if(nums[mid]<target) left=mid+1;
            else { return mid;}
        }
        return -1;
    }
};

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

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/

 暴力解法:把数组从头到尾遍历一遍,并标记 target 第一次和最后一次出现的下标,时间复杂度为 O(n)。

注意到,题目提供的数组为非递减数组,即 nums[ i ] <= nums[ i+1 ]。

在分析问题之前,我们先区分下面两个 mid 的计算:

int mid=left+(right-left)/2;
int mid=left+(right-left+1)/2;

1、如果 nums[ left ] ~ nums[ right ] 中有奇数个数,mid 的两种计算方法没有区别,都会指向同一个下标;

2、如果 nums[ left ] ~ nums[ right ] 中有偶数个数,mid = left + ( right - left )/2 相当于向下取整,mid = left + ( right - left +1 )/2 相当于向上取整

比如下图,left = 0,right = 5,left + ( right - left )/2 = 2.5,但由于 mid 为 整型,最终 mid 为 2(相当于向下取整);mid = left + ( right - left +1 )/2 = 3(相当于对 2.5 向上取整)。

我们先找 target 第一次出现的下标 begin :begin 下标相当于把数组分为两段,下标 0 ~ begin -1 的数小于 target ,下标为 begin ~ nums.size( ) -1 的数大于等于 target

再找出 target 最后一次出现的下标 end :end 下标也把数组分为两段,下标 0 ~ end 的数小于等于 target,下标为 end+1 ~ nums.size( ) -1 的数大于 target

我们可以根据上面的二段性来找 target 第一个和最后一个位置。

先找 begin,left 从 0 开始,right 从 nums.size( ) -1 开始,

1、如果 nums[ mid ] < target,说明下标小于等于 mid 的数一定小于 target,所以 left = mid + 1

2、如果 nums[ mid ] >= target,说明下标大于 mid 的数一定大于  target,但是下标等于 mid 的数可能大于 target,也可能等于 target,所以 right = mid,如果 right = mid -1 ,那么 nums[ mid ] == target  的情况会被跳过,即可能是第一次出现的下标被跳过了;

3、在找 begin 时,mid = left + ( right - left )/2,因为 mid 向下取整才可以找出在一连串连续出现的  target 中找出第一次出现的下标(相当于整体都靠左边找)

4、left = right 时,while 循环结束,停止寻找,我们需要判断 while 循环结束时 nums[ left ] == target,因为数组中可能不存在 target,如果不存在,可以直接返回 -1 了,没有必要找最后一次出现的下标

 在找 end 位置时,也是相似的道理,

1、如果 nums[ mid ] <= target,说明下标小于等于 mid 的数一定小于等于 target,同样,考虑到下标为 mid 的数可能等于 target,所以 left = mid

2、如果 nums[ mid ] > target,说明下标大于等于 mid 的数一定大于  target,所以 right = mid -1

3、在找 end 时,mid = left + ( right - left +1 )/2,因为 mid 向上取整才可以找出在一连串连续出现的  target 中找出最后一次出现的下标(相当于整体都靠右边找)

TIP:如果  right 的计算中出现了 -1,那么 mid 的计算中就会出现 +1

 

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if(nums.size()==0) return {-1,-1};
        int left=0;
        int right=nums.size()-1;
        //找左端点
        while(left<right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]<target) left=mid+1;
            else right=mid;
        }
        int begin=0;
        if(nums[left]!=target) return {-1,-1};
        else begin=left;
        //找右端点
        left=0;
        right=nums.size()-1;
        while(left<right)
        {
            int mid=left+(right-left+1)/2;
            if(nums[mid]>target) right=mid-1;
            else left=mid;
        }
        return {begin,right};
    }
};

搜索插入位置 

35. 搜索插入位置 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/search-insert-position/description/

 同样可以把数组根据 target 分为两段,一边小于 target,一边大于等于 target。

1、如果 nums[ mid ] < target,则 left = mid +1 ;

2、如果 nums[ mid ] >= target,则 right = mid

3、当 left = right 时,退出 while 循环,

a.如果 nums[ left ] < target,说明数组中不存在 target,我们需要把 target 插入到下标为 left +1 的位置中,返回 left +1 ;

b.如果 nums[ left ] > target,同样说明数组中不存在 target,需要把 target 插入到下标为 left 位置中,返回 left;

c.如果 nums[ left ] == target ,说明数组中存在 target,不需要插入,直接返回 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) left=mid+1;
            else right=mid;
        }
        if(nums[left]<target) return left+1;//target比nums[left]大,则在left+1位置插入
        else return left;//target比nums[left]小,则在left位置插入,若相等,则返回在数组中的下标
    }
};

 

相关推荐

  1. 二分查找算法

    2024-04-27 14:54:04       50 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-27 14:54:04       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-27 14:54:04       106 阅读
  3. 在Django里面运行非项目文件

    2024-04-27 14:54:04       87 阅读
  4. Python语言-面向对象

    2024-04-27 14:54:04       96 阅读

热门阅读

  1. IDE 高效快捷键

    2024-04-27 14:54:04       37 阅读
  2. Kubernetes 命令大全

    2024-04-27 14:54:04       31 阅读
  3. Smokeyshell

    2024-04-27 14:54:04       32 阅读
  4. Python编程----递归求解兔子的数量

    2024-04-27 14:54:04       36 阅读
  5. Ansible 清单描述

    2024-04-27 14:54:04       32 阅读
  6. K8S 调试运行中报错的 Pod

    2024-04-27 14:54:04       30 阅读