二分搜索算法相关问题

一、二分搜索(Binary Search):

二分搜索(折半搜索)的Wikipedia定义:是一种在有序数组中查找某一特定元素的搜索算法。从定义可知,运用二分搜索的前提是数组必须是排好序的。另外,输入并不一定是数组,也有可能是给定一个区间的起始和终止的位置。
优点:搜索速度快,时间复杂度为O(logn),即对数时间,也称为对数查找。
缺点:要求待查找的数组或者区间是排好序的。对数组进行动态的删除和插入操作并完成查找,平均复杂度会变为O(n)。此时应当考虑采取自平衡的二叉查找树:

          在 O(nlogn) 的时间内用给定的数据构建出一棵二叉查找树;

          在 O(logn) 的时间里对目标数据进行搜索;

          在O(logn) 的时间里完成删除和插入的操作。
          因此,当输入的数组或者区间是排好序的,同时又不会经常变动,而要求从里面找出一个满足条件的元素的时候,二分搜索就是最好的选择。

二分搜索递归实现 与 非递归实现: 代码如下:

public class BinarySearch {

     int binSearch(int[] nums,int target,int low ,int high){
        if(low> high){
            return -1;
        }
        //取正中间那个数的下标middle
        int middle = low+ (high -low)/2;
        //判断一下正中间的那个数是不是要找的目标数target,是就返回下边middle
        if(nums[middle]== target){
            return  middle;
        }
        //如果发现目标数在左边,就递归的从左半边进行二分搜索
        if(target <nums[middle]){
            return  binSearch(nums,target,low,middle-1);
        } else{//否则从右半边递归进行二分搜索
            return binSearch(nums,target,middle+1 ,high);
        }
    }

    //非递归写法
    int binSearch2(int[] nums,int target,int low ,int high){
         //在while循环里,判断搜索的区间范围是否有效
        while(low <= high){
            //计算正中间的数的下标
            int middle = low+(high-low)/2;
            //判断正中间的那个数是不是要找的目标数target。如果是,就返回下标middle
            if(nums[middle] ==target){
                return  middle;
            }
            //如果发现目标值在左边,调整搜索区间的重点为middle-1,否则调整搜索区间起点为middle+1
            if(target < nums[middle]){
                high = middle-1;
            }else{
                low = middle+1;
            }
        }
        //如果超出了搜索区间,表明无法找到目标值
         return -1;
    }

    public static void main(String[] args){
        int[] arr={1,2,3,4,5,7,9,11};
        int target =9;
       // BinarySearch b= new BinarySearch();
       // int i=b.binSearch(arr, target, 0, (int) (arr.length -1.));
        BinarySearch c= new BinarySearch();
        int n=c.binSearch2(arr, target, 0, (int) (arr.length -1.));
        System.out.println(n);
    }
}

二分搜索核心步骤:

1、确定搜索的范围和区间;

2、取中间的数判断是否满足条件;

3、如果不满足条件,判断应该往哪个半边继续搜索。

二、找确定的边界:

分析:边界分为上边界和下边界,确定的边界指边界的数值等于要找的目标数

public class SearchBound {
    int[] searchBound(int[] nums,int target,int low,int high){

        if(nums ==null || nums.length==0){
            return new int[] {-1,-1};
        }
        int [] res = new int [] {-1,-1};
        res[0]= searchLowerBound(nums,target,low,high);
        res[1]=searchUpperBound(nums,target,low,high);
        return res;
    }
    //查找下边界
    int searchLowerBound(int[] nums,int target,int low,int high){
        if(low>high){
            return -1;
        }
        int middle = low+ (high-low)/2;
        //判断是否为下边界,先看看middle的数是否为target,并判断该数是否为数组的第一个数或它左边的一个数是不是已经比它小,如满足则为下边界
        if(nums[middle]== target &&(middle==0 ||nums[middle-1]<target)){
            return middle;
        }
        if (target <= nums[middle]){
            return searchLowerBound(nums,target,low,middle-1);
        }else {
            return searchLowerBound(nums,target,middle+1,high);
        }
    }
    //查找上边界
    int searchUpperBound(int[] nums,int target,int low,int high){
        if(low> high){
            return -1;
        }
        int middle = low+(high-low)/2;
        //判断是否为上边界时,先看看 middle的数是否为target,并判断该数是否已为数组的最后一个数或者右边的数是不是比它大,如果都满足则为上边界
        if(nums[middle]==target &&(middle==nums.length-1 || nums[middle+1]>target)){
            return middle;
        }
        if(target< nums[middle]){
            return  searchUpperBound(nums,target,low,middle-1);
        }else{
            return searchUpperBound(nums,target,middle+1,high);
        }
    }
    public static void printArray(int[] arr){
        for(int a:arr){
            System.out.println(a);
        }
    }
    public static void main(String[] args){
        int[] arr= {1,2,3,4,7,7,7,8,9};
        int target =7;
        SearchBound  sb = new SearchBound();

        int[] bb = sb.searchBound(arr,target,0,(int)(arr.length-1));
       printArray(bb);
    }
}

三、找模糊的边界:

二分搜索可以用来查找一些模糊的边界。模糊的边界指,边界的值并不等于目标的值,而是大于或者小于目标的值。

例题:从数组 {-2, 0, 1, 4, 7, 9, 10} 中找到第一个大于 6 的数。

//找模糊边界,从{-2,0,1,4,7,9,10}中找到第一个大于6的数
public class MohuBound {
    Integer firstGreaterThan(int[] nums,int target ,int low,int high){
        if(low> high){
            return null;
        }
        int middle = low+ (high- low)/2;
//判断 middle指向的数是否为第一个比target大的数时,需要满足2个条件,middle这个数必须大于target,middle要么是第一个数,
//要么它之前的数小于或者等于target。
if(nums[middle] >target &&(middle==0 ||nums[middle-1]<=target)){ return middle; } if(target< nums[middle]){ return firstGreaterThan(nums,target,low,middle-1); }else{ return firstGreaterThan(nums,target,middle+1,high); } } public static void main(String [] args){ int[] arr= {-2,0,1,4,7,9,10}; int target =6; MohuBound mb = new MohuBound(); Integer a= mb.firstGreaterThan(arr,target,0,(int)(arr.length-1)); System.out.println(a); } }

四、旋转过的排序数组:

public class BinarySearch2 {
    int binSearch(int[] nums,int target,int low, int high){
        if(low> high){
            return -1;
        }
        int middle = low+ (high-low)/2;
//判断中位数是否为要找的数
if(nums[middle]== target){ return middle; }
  
if(nums[low]<= nums[middle]){//判断左半边是不是排好序的 if(nums[low]<= target && target<nums[middle]){//是,则判断目标值是否在左半边 return binSearch(nums,target,low,middle-1);//是则在左半边继续进行二分搜索 } return binSearch(nums,target,middle+1, high); //否在右半边进行二分搜索 }else{ if(nums[middle]<target && target<= nums[high]){//如果右半边是排好序的那一半,判断目标值是否在右边。 return binSearch(nums,target,middle+1,high); //是,则右半边继续进行2分搜索 } return binSearch(nums,target,low,middle-1);//否,在左半边进行2分搜索 } } public static void main(String[] args){ int[] arr={4,5,6,7,0,1,2}; int target=0; BinarySearch2 bs2 = new BinarySearch2(); int bb =bs2.binSearch(arr,target,0,(int)(arr.length-1)); System.out.println(bb); } }

五、不定长边界

例题:有一段不知道具体长度的日志文件,里面记录了每次登录的时间戳,已知日志是按顺序从头到尾记录的,没有记录日志的地方为空,要求当前日志的长度。
解题思路
可以把这个问题看成是不知道长度的数组,数组从头开始记录都是时间戳,到了某个位置就成为了空:{2019-01-14,2019-01-17,…,2019-08-04,….,null,null…}

//例题:有一段不知道具体长度的日志文件,里面记录了每次登录的时间戳,已知日志是按顺序从头到尾记录的,没有记录日志的地方为空,要求当前日志的长度。
//可以把这个问题看成是不知道长度的数组,数组从头开始记录都是时间戳,到了某个位置就成为了空:
//{2019-01-14, 2019-01-17, … , 2019-08-04, …. , null, null, null ...}。
public class BinarySearch3 {
    //先通过getUpperBound函数不断地去试探在什么位置会出现空的日志。即获取high
    int getUpperBound(String[] logs,int high){
        if(logs[high] == null){
            return high;
        }
        return getUpperBound(logs,high * 2);
    }

    int binarySearch(String[] logs,int low,int high){
        if(low> high){
            return -1;
        }
        int middle = low +(high-low)/2;
        if(logs[middle] ==null && logs[middle-1] != null){
            return middle;
        }
        if(logs[middle] == null){
            return binarySearch(logs,low,middle-1);
        }else{
            return binarySearch(logs,middle+1,high);
        }
    }
    int getStringLong(String[] str){
        int high =1;
        int low=0;
        high = getUpperBound(str,high);
      return binarySearch(str,low,high);
    }
    public static void main(String[] args){
        String[] str={"2020-08-01", "2020-08-02","2020-08-03","2020-08-04","2020-08-05",null,null,null,null,null,null,null,null,null,null};
        BinarySearch3 bs3= new BinarySearch3();
        int lens= bs3.getStringLong(str);
        System.out.println(lens);
    }

}
原文地址:https://www.cnblogs.com/goodtest2018/p/13550392.html