1 简介与实例

在有序数组中查找某一数据所在的下标,若存在该数据,返回下标,否则返回-1。

#include <cstdio>

int a[10]={1,3,5,7,9,11,13,15,17,19};
int value;   //待查找元素的值
int index;  //查找到的下标,如果没有查到,返回-1

int search_index(int a[],int n)
{
    int l=0;
    int r=n-1;
    while(l<=r)
    {
        int mid=l+((r-l)>>2);
        if(a[mid]==value)
            return mid;
        else if(a[mid]>value)
            r=mid-1;
        else
            l=mid+1;
    }
    return -1;
}

int main()
{
    printf("请输入您要查询的数据:\n");
    while(scanf("%d",&value)!=EOF)
    {
        index=search_index(a,10);
        printf("您所查询的数据%d所在的下标为:%d\n",value,index);
    }
    return 0;
}

直通BAT-二分查找

以上是左闭右闭的写法,所谓左开右闭就是在[0,n-1]的区间查找,区间的左右边界都可以取到。

#include <cstdio>

int a[10]={1,3,5,7,9,11,13,15,17,19};
int value;   //待查找元素的值
int index;  //查找到的下标,如果没有查到,返回-1

int search_index(int a[],int n)
{
    int l=0;
    int r=n;
    while(l<r)
    {
        int mid=l+((r-l)>>2);
        if(a[mid]==value)
            return mid;
        else if(a[mid]>value)
            r=mid;
        else
            l=mid+1;
    }
    return -1;
}

int main()
{
    printf("请输入您要查询的数据:\n");
    while(scanf("%d",&value)!=EOF)
    {
        index=search_index(a,10);
        printf("您所查询的数据%d所在的下标为:%d\n",value,index);
    }
    return 0;
}

直通BAT-二分查找

以上是左闭右开的写法,即在[0,n)的范围查找,右边的数字取不到。注意这种写法与左闭右闭写法的边界调整的区别。

2 二分的应用,变种与案例

二分查找常见的应用场景:

1 在有序序列中查找一个数,比如上边的例子。

2 并不一定非要在有序序列中得到应用,任意一个序列,只要在二分之后可以淘汰掉一半,那么就可以采用二分法。比如腾讯秋招笔试题目-贪吃的小Q

二分查找常见的考察点:

1 对于边界条件的考察以及代码实现的能力。

2 二分搜索常见题目的变化:

(1)给定处理或查找的对象不同。比如在有重复数值的数组中查找和在无重复数值的数组中查找时,处理情况的细节稍显差异。

(2)判断条件不同。一般需要查找一个数组中等于X的位置,我们也可以查找数组中大于或小于X的位置。

(3)要求返回的内容不同。返回任意一个等于X的位置,返回最早等于X的位置,或者返回等于X的个数等。

3 在有序循环数组中进行二分查找:

     比如数组12345循环之后可以是:12345  23451  34512  45123  51234

二分搜索的重要提醒:

在选择中间节点时,选择的公式为mid=left+(right-left)/2,   防止数据过大时数据溢出。

案例一

 

本题可以用二分法解决。

1 如果数组为空或长度为0,返回-1,表示局部最小位置不存在。

2 如果数组长度为1,返回0,因为0便是局部最小位置。

3 如果数组长度大于1,先检查2头的位置。即:

(1)如果a[0]<a[1],返回0

(2)如果a[n-1]<a[n-2],返回n-1

4 如果2头不满足,说明a[0]>a[1],a[n-1]>a[n-2],所以从最左的位置向右看,有减小的趋势,从最右的位置向左看,也是减小的趋势。此时查看mid位置的数据:

(1)如果Mid位置的数据既比左边小,又比右边小,则为局部最小值。

(2)如果Mid的数据比右边小,比左边大,则mid往左是减小的趋势。取左部分继续查找。

(3)如果Mid的数据比左边小,比右边大,则Mid往右是减小的趋势,取右部分继续查找。

 (4)如果mid的数据比左右都大,任选一侧都可以。

#include <cstdio>

const int maxn=20;
int n;
int a[maxn];
int index;

int find_part_min_index(int a[],int n)
{
    if(n<=0)
        return -1;
    else if(n==1)
        return 0;
    else if(a[0]<a[1])
        return 0;
    else if(a[n-1]<a[n-2])
        return n-1;

    int left=0;
    int right=n-1;
    while(left<=right)
    {
        int mid=left+(right-left)>>1;
        if(a[mid]<a[mid-1]&&a[mid]<a[mid+1])
            return mid;
        else if(a[mid]>a[mid-1]&&a[mid]<a[mid+1])
            right=mid-1;
        else if(a[mid]>a[mid+1]&&a[mid]<a[mid-1])
            left=mid+1;
        else
            left=mid+1;
    }
    return -1;
}

int main()
{
    while(true)
    {
        printf("请输入数组的元素个数:\n");
        scanf("%d",&n);
        printf("请输入数组的元素:\n");
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
        }
        index=find_part_min_index(a,n);
        printf("数组的局部最小位置下标为:%d\n",index);
        printf("\n");
    }
    return 0;
}

直通BAT-二分查找

案例二

给定一个有序数组arr,再给定一个整数num,请在数组中找到数字num这个数出现的最左边的位置。

思路:定义一个全局变量ans,用变量记录数据num最后一次出现的位置。如果最后没有找到,就返回-1.

#include <cstdio>

const int maxn=20;
int ans;
int a[maxn];
int n;
int num=3;

int find_num_of_min_index(int a[],int n)
{
    if(n<=0)
        return -1;
    int left=0;
    int right=n-1;

    while(left<=right)
    {
        int mid=left+(right-left)/2;

        if(a[mid]==num)
        {
            ans=mid;
            right=mid-1;
            continue;
        }

        else if(a[mid]>num)
        {
            right=mid-1;
        }
        else
            left=mid+1;
    }

    if(a[ans]==num)
        return ans;
    else
        return -1;
}

int main()
{
    while(true)
    {
        printf("请输入数组的元素个数:\n");
        scanf("%d",&n);
        printf("请输入数组的元素:\n");
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
        }
        int index=find_num_of_min_index(a,n);
        printf("数组的关于数据%d的最小位置下标为:%d\n",num,index);
        printf("\n");
    }
    return 0;
}

直通BAT-二分查找

案例三

给定一个有序循环数组,返回数组中的最小值。

首先判断a[0]是否小于a[n-1],如果小于,说明数列已经有序,直接返回a[0]即可。

否则,a[0]>=a[n-1]:说明是逆序的:

(1)如果a[0]>a[M],则在0-m的范围找;(注意不是m-1)

(2)如果a[n-1]<a[m]  ,则在m+1----n-1的范围去找

(3)否则,必有a[0]<=a[m],a[m]<=a[n-1],又a[0]>=a[n-1],则a[0]=a[n-1]=a[m]。这个时候的情况就如:2222222221222。只可以通过遍历来确定了。

#include <cstdio>

const int maxn=20;
int min_data;
int a[maxn];
int n;

int find_min_data(int a[],int n)
{
    if(n<=0)
        return -1;
    else if(n==1)
        return a[0];
    else if(a[0]<a[n-1])
        return a[0];
    else
    {
        int left=0;
        int right=n-1;
        while(left<=right)
        {
            int mid=left+(right-left)/2;
            if(a[0]>a[mid])
            {
                right=mid;
                continue;
            }
            else if(a[n-1]<a[mid])
            {
                left=mid+1;
                continue;
            }
            else
            {
                int ans=a[0];
                for(int i=1;i<n;i++)
                {
                    if(ans>a[i])
                        ans=a[i];
                }
                return ans;
            }
        }
        return -1;
    }
}

int main()
{
    while(true)
    {
        printf("请输入数组的元素个数:\n");
        scanf("%d",&n);
        printf("请输入数组的元素:\n");
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
        }
        min_data=find_min_data(a,n);
        printf("数组的最小值:%d\n",min_data);
        printf("\n");
    }
    return 0;
}

直通BAT-二分查找

案例四

给定一个有序数组arr,其中不含有重复元素,请找出满足arr[i]==i的条件最左的位置,如果所有位置上的数都不满足,返回-1。

思路如下:

(1)首先判断a[0]>=N-1,一定不存在。

(2)再判断a[N-1]<=0,同样不存在。

(3)观察中间元素:如果a[mid]>mid,right=mid-1;如果a[mid]<mid,left=mid+1;否则,记录该位置,继续right=mid-1;

#include <cstdio>

int res=-1;  //记录最后出现的位置
const int maxn=100;
int a[maxn];

int find_index(int a[],int n)
{
    if(n<=0)
        return -1;
    if(n==1)
    {
        if(a[0]==0)
            return 0;
        else
            return -1;
    }
    if(a[0]>=n-1)
        return -1;
    if(a[n-1]<=0)
        return -1;
    int left=0;
    int right=n-1;
    while(left<=right)
    {
        int mid=left+(right-left)/2;
        if(a[mid]>mid)
        {
            right=mid-1;
            continue;
        }

        else if(a[mid]>mid)
        {
            left=mid+1;
            continue;
        }

        else
        {
            res=mid;
            right=mid-1;
        }
    }
    return res;
}

int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
        }
        res=find_index(a,n);
        printf("%d\n\n",res);
    }
    return 0;
}

直通BAT-二分查找

案例五

给定一颗完全二叉树头节点head,返回这棵树节点的个数,若完全二叉树节点数为N,请实现时间复杂度低于O(N)的解法。

首先找到二叉树最左边的节点,目的是统计出二叉树的高度/深度。

接着,找到二叉树头节点的右子树的最左边节点,如果该节点可以到达最后一层,说明头节点左子树是一个满二叉树。

直通BAT-二分查找这时,求出左子树节点个数加上根节点,右子树节点个数可以通过递归求取。

如果该节点不可以到达最后一层,说明头节点的右子树是一颗完全二叉树。这时候求右子树节点个数加上根节点,左子树节点个数可以通过递归求得。

直通BAT-二分查找

其时间复杂度为O(logN^2)

案例六

如何更快地求取一个整数k的N次方。若2个整数相乘并得到结果的时间复杂度为O(1),得到整数k的N次方的过程请实现时间复杂度为O(logN)的方法。

法一:

直通BAT-二分查找    N为偶数

直通BAT-二分查找    N为奇数

递归法:

#include <cstdio>

int base,exp;

int PowerWithExponent(int base,unsigned int exp)
{
    if(exp==0)
        return 1;
    if(exp==1)
        return base;
    int result=PowerWithExponent(base,exp>>1);
    result*=result;
    if(exp&0x1==1)
        result*=base;
    return result;
}

int main()
{

    printf("请输入底数和指数:\n");
    scanf("%d%d",&base,&exp);
    int result=PowerWithExponent(base,exp);
    printf("%d\n",result);

    return  0;
}

直通BAT-二分查找

法二:

例如求直通BAT-二分查找,先把75写作二进制形式:1001011

直通BAT-二分查找

那么我们可以先求出直通BAT-二分查找。再根据二进制相应位置是否为1选择性地进行乘法。

 

 

 

相关文章:

  • 2021-07-20
  • 2021-09-19
  • 2022-12-23
  • 2021-10-12
  • 2021-11-17
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-10-31
  • 2022-12-23
相关资源
相似解决方案