二分查找的相关算法题

最近笔试经常遇到二分查找的相关算法题

1)旋转数组中的最小数字

2)在旋转数组中查找某个数

2)排序数组中某个数的出现次数

下面我来一一总结

旋转数组的最小数字

题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1.

实现数组的旋转见左旋转字符串

和二分查找法一样,用两个指针分别指向数组的第一个元素和最后一个元素。

我们注意到旋转之后的数组实际上可以划分为两个排序的子数组,而且前面的子数组的元素都大于或者等于后面子数组的元素。我们还可以注意到最小的元素刚好是这两个子数组的分界线。我们试着用二元查找法的思路在寻找这个最小的元素。

首先我们用两个指针,分别指向数组的第一个元素和最后一个元素。按照题目旋转的规则,第一个元素应该是大于或者等于最后一个元素的(这其实不完全对,还有特例。后面再讨论特例)。

接着我们得到处在数组中间的元素。如果该中间元素位于前面的递增子数组,那么它应该大于或者等于第一个指针指向的元素。此时数组中最小的元素应该位于该中间 元素的后面。我们可以把第一指针指向该中间元素,这样可以缩小寻找的范围。同样,如果中间元素位于后面的递增子数组,那么它应该小于或者等于第二个指针指 向的元素。此时该数组中最小的元素应该位于该中间元素的前面。我们可以把第二个指针指向该中间元素,这样同样可以缩小寻找的范围。我们接着再用更新之后的 两个指针,去得到和比较新的中间元素,循环下去。

按 照上述的思路,我们的第一个指针总是指向前面递增数组的元素,而第二个指针总是指向后面递增数组的元素。最后第一个指针将指向前面子数组的最后一个元素, 而第二个指针会指向后面子数组的第一个元素。也就是它们最终会指向两个相邻的元素,而第二个指针指向的刚好是最小的元素。这就是循环结束的条件。

核心实现代码:

int Min(int *numbers , int length)
{
    if(numbers == NULL || length <= 0)
        return;

    int index1 = 0;
    int index2 = length - 1;
    int indexMid = index1;
    while(numbers[index1] >= numbers[index2])
    {
        if(index2 - index1 == 1)
        {
            indexMid = index2;
            break;
        }

        indexMid = (index1 + index2) / 2;
        //如果下标为index1、index2和indexMid指向的三个数字相等,则只能顺序查找
        if(numbers[index1] == numbers[index2] && numbers[indexMid] == numbers[index1])
            return MinInOrder(numbers , index1 , index2);

        if(numbers[indexMid] >= numbers[index1])
            index1 = indexMid;
        else if(numbers[indexMid] <= numbers[index2])
            index2 = indexMid;
    }
    return numbers[indexMid];
}

//顺序查找
int MinInOrder(int *numbers , int index1 , int index2)
{
    int result = numbers[index1];
    for(int i = index1 + 1 ; i <= index2 ; ++i)
    {
        if(result > numbers[i])
            result = numbers[i];
    }
    return result;
}

注意:当两个指针指向的数字及他们中间的数字三者相同的时候,我们无法判断中间的数字是位于前面的字数组还是后面的子数组中,也就无法移动两个指针来缩小查找的范围。此时,我们不得不采用顺序查找的方法。

2 旋转数组中查找某个数字

要求

给定一没有重复元素的旋转数组(它对应的原数组是有序的),求给定元素在旋转数组内的下标(不存在的返回-1)。

例如

有序数组为{0,1,2,4,5,6,7},它的一个旋转数组为{4,5,6,7,0,1,2}。

  • 元素6在旋转数组内,返回2
  • 元素3不在旋转数组内,返回-1

分析

遍历一遍,可以轻松搞定,时间复杂度为O(n),因为是有序数组旋转得到,这样做肯定不是最优解。有序,本能反映用二分查找,举个例子看看特点

可以看出中间位置两段起码有一个是有序的(不是左边,就是右边),那么就可以在有序的范围内使用二分查找;如果不再有序范围内,就到另一半去找。

参考代码

int search(int A[], int n, int target) {
        int beg = 0;
        int end = n - 1;
        while (beg <= end)
        {
            int mid = beg + (end - beg) / 2;
            if(A[mid] == target)
                return mid;
            if(A[beg]  <= A[mid])
            {
                if(A[beg] <= target && target < A[mid])
                    end = mid - 1;
                else
                    beg = mid + 1;
            }
            else
            {
                if(A[mid] < target && target <= A[end])
                    beg = mid + 1;
                else
                    end = mid - 1;
            }
        }
        return -1;
    }

扩展

上边的有求是没有重复的元素,现在稍微扩展下,可以有重复的元素,其他的要求不变。

思路

大致思路与原来相同,这是需要比较A[beg] 与 A[mid]的关系

  • A[beg]  < A[mid] ————左边有序
  • A[beg]  > A[mid] ————右边有序
  • A[beg]  = A[mid] ————++beg

boolean search(int A[], int n, int target) {
        int beg = 0;
        int end = n - 1;
        while (beg <= end)
        {
            int mid = beg + (end - beg) / 2;
            if(A[mid] == target)
                return true;
            if(A[beg] < A[mid])
            {
                if(A[beg] <= target && target < A[mid])
                    end = mid - 1;
                else
                    beg = mid + 1;
            }
            else 0if(A[beg] > A[mid])
            {
                if(A[mid] < target && target <= A[end])
                    beg = mid + 1;
                else
                    end = mid - 1;
            }
            else
                ++beg;
        }
        return false;
    }

3 数字在排序数组中的出现

<span style="font-size:18px;">//二分查找,二分查找key第一次出现的位置,二分查找最后一次出现的key

//返回两者相减+1或者找到第一次出现的位置,向后查找int binarySearchFirstPos(int * iArr, int l, int h, int key)

{

    while(l <= h )

    {

        int mid  = (l + h) / 2;

        if(iArr[mid] < key)

            l = mid +1;

        elseif(iArr[mid] > key)

            h = mid - 1;

        else

        {

            if(mid == l || iArr[mid - 1] != key)

                return mid;

            else 

                h = mid - 1;

        }

    }

    return -1;

}

int binarySearchLastPos(int * iArr, int l, int h, int key)

{

    while(l <= h)

    {

        int mid = (l + h) / 2;

        if(iArr[mid] < key)

            l =  mid + 1;

        elseif(iArr[mid] > key)

            h = mid - 1;

        else

        {

            if(mid == h || iArr[mid + 1] != key)

                return mid;

            else

                l = mid + 1;

        }

    }

    return -1;

}

int numOfKey(int * iArr, int length, int key)

{

    int firstPos = binarySearchFirstPos(iArr, 0, length - 1, key);

    int lastPos = binarySearchLastPos(iArr, 0, length - 1, key);

    cout << firstPos << "\t" << lastPos << endl;;

    if(firstPos == -1)

        return0;

    elsereturn lastPos - firstPos + 1;

}</span>

今天就写到这里了,睡觉了。

时间: 2024-11-03 21:10:49

二分查找的相关算法题的相关文章

算法题16 二分查找及相关题目

二分查找思想就是取中间的数缩小查找范围,对应不同的题目变形,在取到中间数mid确定下一个查找范围时也有不同,左边界有的low=mid+1,也可能low=mid,右边界有的high=mid-1,也有可能high=mid. 对于一个排序数组来说二分查找的时间复杂度是O(logn) 1. 二分查找法 1 int BinarySearch(int arr[],int len,int target) 2 { 3 if (arr==NULL||len<=0) 4 throw std::exception(&qu

Java笔记(07):常见对象--StringBuffer、二分查找及排序算法

1.StringBuffer类的构造方法 1 package cn.itcast_01; 2 3 /* 4 * 线程安全(多线程讲解) 5 * 安全 -- 同步 -- 数据是安全的 6 * 不安全 -- 不同步 -- 效率高一些 7 * 安全和效率问题是永远困扰我们的问题. 8 * 安全:医院的网站,银行网站 9 * 效率:新闻网站,论坛之类的 10 * 11 * StringBuffer: 12 * 线程安全的可变字符串. 13 * 14 * StringBuffer和String的区别? 1

二分查找与快速排序算法

1 /**********二分查找*****************/ 2 int half_find(int *num,int size, int a) 3 { 4 int i=0; 5 int low=0; 6 int high=size-1; 7 int mid;//记录中间位置 8 while(low<=high) 9 { 10 mid = (low+high)/2; 11 if(num[mid] == a) 12 return mid;//返回所在位置 13 if(num[mid] >

链表相关算法题总结 1

链表题目对算法的要求度不高,但实际写的过程中需要注意语言细节,考虑精细度的地方很多. 1.链表结构与基本操作 1.1 添加节点 一般情况: cur ->next = prev ->next; prev ->next = cur; 表头插入: cur ->next = head; head = cur; 1.2删除节点 一般情况:(已知待删除节点的前驱节点) ListNode* temp = prev->next; prev->next = prev->next-&

leetcode sum相关算法题

1. Two Sum(https://oj.leetcode.com/problems/two-sum/) 解题思路: 解法一: 暴力,O(n2)时间复杂度,TLE 解法二:利用hash, 记录下数组中每个值对应的下标,再遍历一遍数组,通过查看target-num[i]的值是否在map中来确定另一个数值.时间复杂度O(n) 解法三:对num数组排序,O(nlog(n)), 然后左右夹逼O(n). 但这道题要求记录下标,故这个方法行不通. python代码如下: 1 def twoSum(self

【经典算法——查找】二分查找

二分查找又称为折半查找,仅适用于事先已经排好序的顺序表.其查找的基本思路:首先将给定值K,与表中中间位置元素的关键字比较,若相等,返回该元素的存储位置:若不等,这所需查找的元素只能在中间数据以外的前半部分或后半部分中.然后在缩小的范围中继续进行同样的查找.如此反复直到找到为止.算法如下: 1 template<typename T> 2 int BinarySearch(vector<T> &data, T key) { 3 int low = 0, high = data

程序员,你应该知道的二分查找算法

原理 二分查找(Binary Search)算法,也叫折半查找算法.二分查找的思想非常简单,有点类似分治的思想.二分查找针对的是一个有序的数据集合,每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为 0. 为了方便理解,我们以数组1, 2, 4, 5, 6, 7, 9, 12, 15, 19, 23, 26, 29, 34, 39,在数组中查找26为例,制作了一张查找过程图,其中low标示左下标,high标示右下标,mid标示中间值下标 二分查

算法:支持重复元素的二分查找

近几天在处理的一个项目,需要频繁对一些有序超大集合进行目标查找,二分查找算法是这类问题的最优解.但是java的Arrays.binarySearch()方法,如果集合中有重复元素,而且遇到目标元素正好是这些重复元素之一,该方法只能返回一个,并不能将所有的重复目标元素都返回,没办法,只能自造轮子了. 先复习下二分查找的经典算法: 1 private int binarySearch1(Integer[] A, Integer x) { 2 int low = 0, high = A.length

java 二分查找 - 折半查找算法

二分查找: 这个算法是比较简单的,容易理解的.这个算法是对有序的数组进行查找,所以想要使用这个算法那么 首先先要对数组进行排序. 其实有三个指针,开始指针,末尾指针,中间指针,来开始.折半查找. 步骤如下: 1.确定三个指针,start,end,middleIndex. 2.判断start<=end,如果满足,就执行这个方法,不满足,就返回,找不到. 3.在2的前提下,我们对其折半查找,middleIndex = start+end >> 1,取中间值. 4.判断中间位置的值和目标值是否