【总结】二分查找

目录

二分法的原理

1.二分查找(模板1)

2.二分查找(模板2)

例题

1.x的平方根(leetcode 69)

2.Pow(x,n)(leetcode 50)

3.山脉数组的峰顶索引(leetcode 852)

4.山脉数组中查找目标值(leetcode 1095)

5.有序数组中的单一元素 (leetcode 540)


  • 二分法的原理

二分查找,又叫做折半查找,是一种在有序数组中查找某一特定元素的搜索算法。

1.二分查找(模板1)

这是我们经常见到的二分查找算法,主要作用是在循环体中查找某一元素,主要存在三个分支,其中两个分支用于边界的搜索。

Binary search into array.png

int Binary_search(int arr[],int n,int target)
{
    //在[l,r]闭区间搜索target
	int l=0,r=n-1; 
	while(l<=r)//当l==r时,区间依然有效 
	{
		int mid=l+(r-l)/2;
		if(arr[mid]==target)
			return mid+1;	
		else if(arr[mid]<target)
			l=mid+1;//在[mid+1,r]闭区间搜索target	
		else
			r=mid-1;//在[l,mid-1]闭区间搜索target
	}
	return -1;
 }

2.二分查找(模板2)

这种二分查找,主要是在循环体内缩小搜索区域,当退出循环以后区间只剩下一个元素,视情况单独判断。

int search(int nums[],int left,int right,int target)
{
	//在[left,right]里查找target
	while(left<right)
	{
		//选取中位数时下取整 
		int mid=left+(right-left)/2;
		if(check(mid))
		{
			//下轮搜索区间[mid+1,rihjt]
			left=mid+1; 
		}
		else
		{
			//下轮搜索区间[left,mid]
			right=mid;
		}
	 }
	 //视情况而定,判断left或者right是否符合题意。 
 } 

int search(int nums[],int left,int right,int target)
{
	//在[left,right]里查找target
	while(left<right)
	{
		//选取中位数时上取整 
		int mid=left+(right-left+1)/2;
		if(check(mid))
		{
			//下轮搜索区间[left,mid-1]
			right=mid-1; 
		}
		else
		{
			//下轮搜索区间[mid,right]
			left=mid;
		}
	 }
	 //视情况而定,判断left或者right是否符合题意。 
 } 
  • 例题

1.x的平方根(leetcode 69

  • 题目描述:

实现 int sqrt(int x) 函数。

计算并返回 x 的平方根,其中 x 是非负整数。

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

示例 1:

输入: 4
输出: 2

示例 2:

输入: 8
输出: 2
说明: 8 的平方根是 2.82842..., 
     由于返回类型是整数,小数部分将被舍去。

  • 分析:

首先,我们知道一个非负整数 x 的平方根在[ 0 ,x]之间,而且这个区间是有序的。那么我们就可以使用二分查找法。

  1. 如果中间元素 mid 等于x的平方根,那么直接返回。
  2. 如果x/mid的值大于mid,说明x的平方根在mid的右边,则l=mid+1;
  3. 如果x/mid的值小于mid,说明x的平方根在mid的左边,则r=mid-1;
  4. 如果没找到合适的值,则返回向下取整的值min(l,r),其实就是r,因为当不满足l<=r时,r<l;
class Solution {
public:
    int x_sqrt(int x)
    {
        if(x<2)
            return x;
        int l=2;
        int r=x/2;
        while(l<=r)
        {
            int mid=l+(r-l)/2;
            if(x/mid==mid)
                return mid;
            else if(x/mid>mid)
                l=mid+1;
            else
                r=mid-1;
        }
        return r;
    }
    int mySqrt(int x) {
        return x_sqrt(x);
    }
};

2.Pow(x,n)(leetcode 50

  •  题目描述:

实现 pow(x, n) ,即计算 x 的 n 次幂函数。

示例 1:

输入: 2.00000, 10
输出: 1024.00000


示例 2:

输入: 2.10000, 3
输出: 9.26100


示例 3:

输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25


说明:

-100.0 < x < 100.0
n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。

  • 分析:

这道题计算x的平方,我们可以将x进行分组,分成一等分,一等分的,然后在将每一等分合并起来,组成最后的结果,这里有点类似于归并排序的过程。

    double powN(double x,long long l,long long r)
    {
        if(l>=r)
            return x;
        long long mid=l+(r-l)/2; 
        double left=powN(x,l,mid);
        double right=powN(x,mid+1,r);
        return left*right;
    }

注:这是我自己的实现,但是在最后会超时。 

    double powHelper(double x,long long N)
    {
        if(N==0)
            return 1.0;
        double y=powHelper(x,N/2);
        return N%2==0?y*y:y*y*x;
    }
class Solution {
public:
    double powN(double x,long long l,long long r)
    {
        if(l>=r)
            return x;
        long long mid=l+(r-l)/2; 
        double left=powN(x,l,mid);
        double right=powN(x,mid+1,r);
        return left*right;
    }
    double powHelper(double x,long long N)
    {
        if(N==0)
            return 1.0;
        double y=powHelper(x,N/2);
        return N%2==0?y*y:y*y*x;
    }
    double myPow(double x, int n) {
        long long N=n;
        if(N==0)
            return 1;
        else if(N<0)
            // return 1.0/powN(x,1,N*-1);
            return 1.0/powHelper(x,N*-1);
        else   
            // return powN(x,1,N);
            return powHelper(x,N);
    }
};

3.山脉数组的峰顶索引(leetcode 852

  • 题目描述:

我们把符合下列属性的数组 A 称作山脉:

A.length >= 3,存在 0 < i < A.length - 1 使得A[0] < A[1] < ... A[i-1] < A[i] > A[i+1] > ... > A[A.length - 1]
给定一个确定为山脉的数组,返回任何满足 A[0] < A[1] < ... A[i-1] < A[i] > A[i+1] > ... > A[A.length - 1] 的 i 的值。

示例 1:

输入:[0,1,0]
输出:1

示例 2:

输入:[0,2,1,0]
输出:1
 

提示:

3 <= A.length <= 10000
0 <= A[i] <= 10^6
A 是如上定义的山脉

  • 分析:

 我们需要找到山峰,可以通过线性查找,当A[i]>A[i+1]时,结束遍历,这样的时间复杂度为O(n),为了能够降低时间复杂度,我们可以使用二分查找。

  • 当A[mid]<A[mid+1]时,处于山峰的左边,索引 mid 一定不是峰顶的位置,搜索峰顶的位置在 mid 的右边(不包括i);
  • 当A[mid]>A[mid+1]时,处于山峰的右边,索引 mid 可能是峰顶的位置,搜索峰顶的位置在 mid 的左边(包括i);
class Solution {
public:
    int peakIndexInMountainArray(vector<int>& A) {
        int l=0;
        int r=A.size()-1;
        while(l<r)
        {
            int mid=l+(r-l)/2;
            if(A[mid]>A[mid+1])
                r=mid;
            else
                l=mid+1;
        }
        return l;
    }
};

4.山脉数组中查找目标值(leetcode 1095

  • 题目描述

(这是一个 交互式问题 )

给你一个 山脉数组 mountainArr,请你返回能够使得 mountainArr.get(index) 等于 target 最小 的下标 index 值。

如果不存在这样的下标 index,就请返回 -1。

何为山脉数组?如果数组 A 是一个山脉数组的话,那它满足如下条件:

首先,A.length >= 3

其次,在 0 < i < A.length - 1 条件下,存在 i 使得:

A[0] < A[1] < ... A[i-1] < A[i]
A[i] > A[i+1] > ... > A[A.length - 1]

你将 不能直接访问该山脉数组,必须通过 MountainArray 接口来获取数据:

MountainArray.get(k) - 会返回数组中索引为k 的元素(下标从 0 开始)
MountainArray.length() - 会返回该数组的长度

注意:

对 MountainArray.get 发起超过 100 次调用的提交将被视为错误答案。此外,任何试图规避判题系统的解决方案都将会导致比赛资格被取消。

为了帮助大家更好地理解交互式问题,我们准备了一个样例 “答案”,请注意这 不是一个正确答案。

示例 1:

输入:array = [1,2,3,4,5,3,1], target = 3
输出:2
解释:3 在数组中出现了两次,下标分别为 2 和 5,我们返回最小的下标 2。


示例 2:

输入:array = [0,1,2,4,2,1], target = 3
输出:-1
解释:3 在数组中没有出现,返回 -1。

提示:

3 <= mountain_arr.length() <= 10000
0 <= target <= 10^9
0 <= mountain_arr.get(index) <= 10^9

  •  分析:

这道题有几个重要的信息:

  1. 山脉数组是两个有序数组的组成的数组,存在一个山峰值。
  2. 对 MountainArray.get 发起超过 100 次调用的提交将被视为错误答案。
  3. 数据规模:3 <= mountain_arr.length() <= 10000

从这些信息中,我们得知:我们对其峰值进行查找需要使用一次二分查找,将峰值前半部分的查找需要使用一次二分查找,然后峰值后半部分的查找需要使用一次二分查找,一共三次二分查找

/**
 * // This is the MountainArray's API interface.
 * // You should not implement it, or speculate about its implementation
 * class MountainArray {
 *   public:
 *     int get(int index);
 *     int length();
 * };
 */

class Solution {
public:
    int binnary_search(MountainArray &mountainArr,int start,int end,int target,bool flag)
    {
        int l=start;
        int r=end;
        while(l<=r)
        {
            int mid=l+(r-l)/2;
            if(mountainArr.get(mid)==target)
                return mid;
            if(flag)
            {
                if(mountainArr.get(mid)>target)
                {
                    r=mid-1;
                }
                else
                    l=mid+1;   
            }
            else
            {
                if(mountainArr.get(mid)<target)
                {
                    r=mid-1;
                }
                else
                    l=mid+1;
            }

        }
        return -1;
    }
    int findInMountainArray(int target, MountainArray &mountainArr) {
        int length=mountainArr.length();
        if(length<3)
            return -1;
        int l=0;
        int r=length-1;
        while(l<r)
        {
            int mid=l+(r-l)/2;
            if(mountainArr.get(mid)>mountainArr.get(mid+1))
                r=mid;
            else
                l=mid+1;
        }
        int prior=binnary_search(mountainArr,0,l,target,true);
        if(prior!=-1)
            return prior;
        int back=binnary_search(mountainArr,l+1,length-1,target,false);
        return back;

    }
};

5.有序数组中的单一元素 (leetcode 540)

  • 题目描述:

给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。

示例 1:

输入: [1,1,2,3,3,4,4,8,8]
输出: 2


示例 2:

输入: [3,3,7,7,10,11,11]
输出: 10

注意: 您的方案应该在 O(log n)时间复杂度和 O(1)空间复杂度中运行。

  • 分析:

从注意事项中,可以得知这道题算法时间复杂度需要为 O(log n),那么就会想到用二分查找.

  1. 首先我们需要知道,数组中的元素数量为奇数,元素相对有序。
  2. 当mid=mid+1时,如果mid右边的元素个数为偶数,说明mid右边含有偶数个数,那么去掉mid+1后,就为奇数个,说明单一元素在mid+1的右边(l=mid+2)。
  3. 当mid=mid+1时,如果mid右边的元素个数为奇数,说明mid右边含有奇数个数,那么去掉mid+1后,就为偶数个,说明单一元素在mid的左边(r=mid-1)。
  4. 当mid=mid-1时,如果mid右边的元素个数为偶数,说明mid右边含有偶数个数,那么说明单一元素在mid-1的左边(r=mid-2)。
  5. 当mid=mid-1时,如果mid右边的元素个数为奇数,说明mid右边含有奇数个数,那么说明单一元素在mid的右边(l=mid+1)。
  6. 上述情况都不是,就直接返回该值。
class Solution {
public:
    int binary_search(vector<int>&nums)
    {
        int l=0;
        int r=nums.size()-1;
        while(l<r)
        {
            int mid=l+(r-l)/2;
            bool flag=(r-mid)%2==0;
            //中间元素和相邻右边的元素相等
            // cout<<"mid: "<<mid<<" l: "<<l<<" r: "<<r<<endl;
            if(nums[mid]==nums[mid+1])
            {
                //右半部分数量为偶数
                if(flag)
                    l=mid+2;
                else
                    r=mid-1;
                    
            }
            else if(nums[mid]==nums[mid-1])
            {
                //右半部分数量为偶数
                if(flag)
                    r=mid-2;
                else
                    l=mid+1;
            }
            else
                return nums[mid];
        }
        return nums[l];
    }
    int singleNonDuplicate(vector<int>& nums) {
        // if(nums.empty())
        //     return 0;
        // int a=0;
        // for(auto num:nums)
        //     a^=num;
        // return a;
        int a=binary_search(nums);
        return a;
    }
};

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

火柴的初心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值