从二分法到搜索区间题解的思路扩展

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qilei2010/article/details/51184395

题目

给定一个包含 n 个整数的排序数组,找出给定目标值 target 的起始和结束位置。如果目标值不在数组中,则返回[-1, -1]。
给出[5, 7, 7, 8, 8, 10]和目标值target=8,返回[3, 4]

题目链接

二分解题思路

对有序数组应用二分法,能快速缩小目标范围,达到O(logN)。搜索区间和搜索单个元素类似,但也有不同思路。

一是先二分查找 first 第一次出现位置,再二分查找一次查找 last 最后一次出现位置,二者合并就是区间。代码参考

二是只进行一次二分(仅为了缩小目标区间),若找到目标元素target,就直接在当前二分范围的startend之间,从当前target位置往前往后逐项比对,找到全部相同元素。

我这里列出方法二的代码。

class Solution {
    /** 
     *@param A : an integer sorted array
     *@param target :  an integer to be inserted
     *return : a list of length 2, [index1, index2]
     */
public:
    vector<int> searchRange(vector<int> &A, int target) {
        vector<int> range;
        range.push_back(-1);
        range.push_back(-1);
        int start  = 0, end = (int)A.size() - 1;
        int mid = 0 ;
        //快速排除
        if (A.size() == 0 || A[0] > target || A[end] < target) {
            return range;
        }
        //二分模板
        while (start + 1 < end) {
            mid = start + (end - start)/2;
            if (A[mid] < target) {
                start = mid;
            }else if(A[mid] > target){
                end = mid;
            }else{
                //分别往前往后循环到start end
                int i = 0, j = 0;
                for(i = mid - 1; i >= start; i--){
                    if(A[i] != target ){
                        break;
                    }
                }
                range[0] = i + 1;

                for(j = mid + 1; j <= end; j++){
                    if(A[j] != target ){
                        break;
                    }
                }
                range[1] = j - 1;

                return range;
            }
        }

        //检查首尾
        if(A[start] == target){
            range[0] = start;
            range[1] = start;
        }

        if(A[end] == target){
            range[0] = end;
            range[1] = end;
        }
        return range;
    }
};

参考如图:

[5,7,7,8,8,10]      target = 7
 s   m      e       <- 第一轮二分,找到target
  <- t ->           <- 分别往左右找7
 i   t j            <- i!=target, i+1即区间左坐标
                    <- j!=target, j-1即区间右坐标

当数据少时可能看不出来优势,当数据几千几万个时,目标元素区间一般很小,这时一次二分快速定位目标元素后,再往左右找会很快(因为目标元素相对全部数据来说一般比较少,循环很快会结束)。

附:二分模板

最后再附上一个经典的二分模板,记录下来以后自己也能快速回查:
重点理解:
1. 循环结束的判断条件 start + 1 < end [start 和 end 不相邻]
2. 循环结束后对比 start end

//求非降序集合内目标第一次出现的位置
int binarySearch(vector<int> &array, int target) {
        // write your code here
        int start = 0, end = (int)array.size() - 1;
        int mid = 0;

        while (start + 1 < end) {
            mid = start + (end - start)/2;
            if (array[mid] == target) {
                end = mid;  //常见错误->return mid; 错误范例[3,3,3,3,3]
            }else if(array[mid] > target){
                end = mid;
            }else{
                start = mid;
            }
        }

        if (target == array[start]) {
            return start;
        }

        if (target == array[end]) {
            return end;
        }

        return -1;
    }

如图:

[5,7,7,8,9]
 s   m   e
 s m e      
 s m        <- start + 1 = end 达到退出条件
            <- 接着先比较 start 再比较 target

若要是求最后一个元素位置呢?
很简单,只需要改动如下位置:

if (array[mid] == target) {
                start = mid;  //将 mid 赋值给 start
                //使检索目标区域向右移
...
//先比较 end ,再比较 start,因为你是求目标的最后一个位置嘛
        if (target == array[end]) {
            return end;
        }

        if (target == array[start]) {
            return start;
        }

如图:

[5,7,7,8]
 s m   e
   s m e
     s e      <- start + 1 = end 达到退出条件
              <- 接着先比较 end 再比较 target

猜你喜欢

转载自blog.csdn.net/qilei2010/article/details/51184395