【leetcode】二分查找专题

在这里插入图片描述

对于二分查找,相信大家都再熟悉不过了。一旦数据是有序的,我们大概率会想到二分,但是有时候数据不是有序时,也可以使用二分。下面我们就来写几道关于二分的题目。

1.基本的二分查找

在这里插入图片描述

题目链接

二分的精髓就在于它一次就可以决定一半数据的归属,该题就是一个最简单的二分题。

在这里插入图片描述

由于left和right相遇时可能就是target,所以循环的条件应该是left <= right

当left与right错开时,就说明没有target这个元素,返回-1

如果数据太多,left和right较大时,我们计算mid时,left + right可能会溢出;
所以我们可以这样 mid = left + (right - left) / 2 ;

    int search(vector<int>& nums, int target) {
        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 if(nums[mid] > target)
                right = mid - 1;
            else
                return mid;
        }
        return -1;
    }

2.使用二分 查找左右端点

2.1 左右端点

在这里插入图片描述
题目链接

对于该题目,就是在朴素二分的基础上,让你在区间中选择两个位置,该位置标记了target元素的开始与结束。

但是我们这一题需要在朴素二分的基础上进行变化。

在这里插入图片描述


在这里插入图片描述

    vector<int> searchRange(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size()-1;
        if(nums.size() == 0)
            return {-1,-1};

        int begin = -1;
        int end = -1;
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            //小于target,去掉[left,mid]区间
            if(nums[mid] < target)
                left = mid + 1;
            //大于等于target,去掉(mid,right]
            else
                right = mid;
        }
        if(nums[left] != target)
            return {-1,-1};
        
        begin = left;

        left = 0;
        right = nums.size() - 1;

        while(left < right)
        {
            int mid = left + (right - left + 1) / 2;
            //小于等于target,去掉[left,mid)区间
            if(nums[mid] <= target)
                left = mid;
            //大于target,去掉[mid,right]区间
            else
                right = mid - 1;
        }   
        if(nums[left] != target)
            return {-1,-1};
        end = left;
        return {begin,end};
    }

在这里插入图片描述

2.2 二分模板

求左端点

        while (left < right)
        {
            int mid = left + (right - left) / 2;
            //小于target,去掉[left,mid]区间
            if (...)
                left = mid + 1;
            //大于等于target,去掉(mid,right]
            else
                right = mid;
        }

求右端点

        while (left < right)
        {
            int mid = left + (right - left + 1) / 2;
            //小于等于target,去掉[left,mid)区间
            if ()
                left = mid;
            //大于target,去掉[mid,right]区间
            else
                right = mid - 1;
        }

3.搜索插入位置

在这里插入图片描述

由于该题目也具有二段性,因此也使用二分。、

  • 当left与right相遇时
    • 如果 nums[left] < target,则表明数组中没有target元素,则target元素应插入到left后面
    • 如果 nums[left] > target,则表明数组中没有target元素,left位置就是target要插入的位置
    • 如果 nums[left] == target,则表明数组中有target元素,left位置就是target的下标
    int searchInsert(vector<int>& nums, int target) {
        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;
        }

        if(nums[left] < target)
            return left + 1;
        else 
            return left;
    }

4.x的平方根

在这里插入图片描述
题目链接

解法一:暴力查找

使用 i 依次遍历1-x的数,直到 i*i >= x时,就可以出现结果。为了防止溢出,应使用long long

    int mySqrt(int x) {
        for(long long i=1; i<=x; i++)
        {
            if(i* i == x)
                return i;
            else if(i *i > x)
                return i-1;
        }
        return 0;
    }

解法二:二分查找

对于1-x之间的数,它平方的结果具有二段性,要么> x,要么 <= x,因此可以使用二分优化暴力解法。

    int mySqrt(int x) {
        if(x == 0)
            return 0;
        int left = 1;
        int right = x;
        while(left < right)
        {
            long long mid = left + (right - left + 1) / 2;
            if(mid * mid <= x)
                left = mid;
            else
                right = mid - 1;
        }
        return left;
    }

5.山脉数组的顶峰

在这里插入图片描述
题目链接

根据题目可知,山脉递增以后再递减,那么一定会存在相邻的两个数前者大于后者的情况,此时前者就是峰顶。

解法一:暴力求解

从i = 1下标依次遍历数组,如果nums[i-1] > nums[i],此时i-1位置就是峰顶。

解法二:二分求解

  • 如果nums[i] > nums[i-1],那么nums[i]之前一定是递增的,峰值还再后面,因此可以排除 i 之前的区间 left = mid;
  • 如果nums[i] < nums[i-1],那么i及后面的都不可能是峰值,right = mid - 1;
  • 未避免死循环和mid-1越界,求mid时应选择后面的一个
    int peakIndexInMountainArray(vector<int>& arr) {
        int left = 0;
        int right = arr.size()-1;

        while(left < right)
        {
            int mid = left + (right - left + 1)/ 2;
            if(arr[mid] > arr[mid-1])
                left = mid;
            else
                right = mid - 1;
        }
        return left;
    }

6.寻找峰值

在这里插入图片描述

任取⼀个点 i ,与前⼀个点 i - 1,会有如下两种情况:

  • arr[i] > arr[i - 1] :此时「右侧区域」⼀定会存在山峰(因为最右侧是负⽆穷),那么我们可以去右侧去寻找结果;
  • arr[i] < arr[i - 1] :此时「左侧区域」⼀定会存在山峰(因为最左侧是负⽆穷),那么我们可以去左侧去寻找结果。
  • 注意边界情况
    int findPeakElement(vector<int>& nums) 
    {
        int left = 0;
        int right= nums.size()-1;
        while(left < right)
        {
            //防止下标越界和死循环,mid总是取后一个
            int mid = left + (right - left + 1) / 2;

            if(nums[mid] > nums[mid-1])//去右边区域找
                left = mid;
            else
                right = mid - 1;
        }
        return left;
    }

7.寻找旋转排序数组中的最小值

在这里插入图片描述
题目链接

由于数组原本是一个有序的数组,那么旋转后数组就会变成含有两段有序元素的数组。
在这里插入图片描述

对于任意一个点 i 而言,我们就是要找C的位置

  • 如果nums[i]落在AB区间中,也就是nums[i] > nums[D],此时应该去 [i+1,D]区间中找,即left = mid + 1
  • 如果nums[i]落在CD区间中,也就是nums[i] < nums[D],此时应该去[C,i]区间中找,即right = mid;
  • 当区间长度变为1时,就是结果
    int findMin(vector<int>& nums) {
        int n = nums.size()-1;
        int left = 0;
        int right = n;
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] > nums[n])
                left = mid + 1;
            else
                right = mid;
        }
        return nums[left];
    }

在这里插入图片描述

评论 45
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值