二分搜索、折半查找


二分法

二分法存在多种写法!!!

Alg.1 : [left, right]

查找数组中某一个数据 data,存在返回下标,不存在返回 -1;

public int binary(int[] arr, int target) {
        int min = 0;	
        int max = arr.length - 1;		// 1. max 的取值
        int mid;
        while (min <= max) {			// 2. while的判断
            mid = (min + max) / 2; 		// 3. 中间值的计算
            if (arr[mid] > target) {
                max = mid - 1;  		// 4. min 下一个值的变化
            } else if (arr[mid] < target) {
                min = mid + 1;			// 5. max 下一个值的变化
            } else {
                return mid;				// 6. 返回值的情况
            }
        }
        return -1;
    }
  1. max 的取值
    a. max = arr.length - 1;
    b. max = arr.length;

区别是:前者相当于两端都闭区间 [left, right],后者相当于左闭右开区间 [left, right),因为索引大小为 nums.length 是越界的。在这儿选择 max = arr.length - 1; ,则在第二步一般应该选择 min <= max

  1. while的判断
    a. min <= max
    b. min < max

while(left <= right) 的终止条件是 left == right + 1,写成区间的形式就是 [right + 1, right]。只有这样区间中才没有数据。
或者带个具体的数字进去 [3, 2],可见这时候区间为空,因为没有数字既大于等于 3 又小于等于 2 的吧。所以这时候 while 循环终止是正确的,直接返回 -1 即可。


while(left < right) 的终止条件是 left == right,写成区间的形式就是 [left, right],或者带个具体的数字进去 [2, 2],这时候区间非空,还有一个数 2,但此时 while 循环终止了。也就是说这区间 [2, 2] 被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的。

1 和 2 具有相关性。

  1. 中间值的计算
    a. (max + min) / 2; //可能越界
    b. min + (max - min) / 2;
    c. min + ((max - min) >>> 1);
  2. min 下一个值的变化
    a. min =mid +1;
  3. max 下一个值的变化
    a. max =mid -1;
    b. max =mid ;

本算法的搜索区间是两端都闭的,即 [left, right]。那么当我们发现索引 mid 不是要找的 target 时,下一步应该去搜索 [left, mid-1] 或者 [mid+1, right],因为 mid 已经搜索过,应该从搜索区间中去除。

  1. 返回值的情况

Alg.2 : 左侧边界(有序数组中小于 target 的数的数目)

查找数组中某一个数据 data左侧边界,存在返回下标,不存在返回 -1;
比如:在 [1 2 2 2 3] 查找 target = 2 的出现在最左边的位置,这儿应该是下标 1;
这儿还可以有另一种理解:有序数组中小于 target 的数的数目。

int left_bound(int[] nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0;
    int right = nums.length-1; 

    while (left <= right) { // 注意
        int mid = (left + right) / 2;
        if (nums[mid] == target) {		
            right = mid-1;					// 1. 关键
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid-1; // 注意
        }
    }
    if(left >= nums.length ) // 2. left 越界(不存在target), 但是数组中比target小的值有 left 个
    	return left;
    if(nums[left] != target) // 3. 这时候返回的含义是:不存在target,但是数组中比target小的值有 left 个
    	return left;
    return left;
    						 // 4. right = left -1 
}
  1. if (nums[mid] == target) { right = mid-1; }

找到 target 时没有马上返回,而是缩小「搜索区间」的上界 right,在区间 [left, mid]中继续搜索,即不断向左收缩,达到锁定左侧边界的目的。

Alg.3 : [left, right)

查找数组中某一个数据 data,存在返回下标,不存在返回 -1;

public int binary(int[] nums, int target) {
        int min = 0;	
        int max = nums.length;			// 1. max 的取值
        int mid;
        while (min < max) {				// 2. while的判断
            mid = (min + max) / 2; 		// 3. 中间值的计算
            if (nums[mid] > target) {
                max = mid;  			// 4. min 下一个值的变化
            } else if (nums[mid] < target) {
                min = mid + 1;			// 5. max 下一个值的变化
            } else {
                return mid;				// 6. 返回值的情况
            }
        }
        return -1;
    }
  1. max 的取值
    a. max = arr.length - 1;
    b. max = arr.length;

区别是:前者相当于两端都闭区间 [left, right],后者相当于左闭右开区间 [left, right),因为索引大小为 nums.length 是越界的。在这儿选择 max = arr.length; ,则在第二步一般应该选择 min < max

  1. while的判断
    a. min <= max
    b. min < max

while(left < right) 的终止条件是 left == right,写成区间的形式就是 [left, right],或者带个具体的数字进去 [2, 2],这时候区间非空,还有一个数 2,但此时 while 循环终止了。也就是说这区间 [2, 2] 被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的。

  1. 中间值的计算
    a. (max + min) / 2; //可能越界
    b. min + (max - min) / 2;
    c. min + ((max - min) >>> 1);
  2. min 下一个值的变化
    a. min =mid +1;
  3. max 下一个值的变化
    a. max =mid -1;
    b. max =mid ;

本算法的搜索区间是两端都闭的,即 [left, right]。那么当我们发现索引 mid 不是要找的 target 时,下一步应该去搜索 [left, mid) 或者 [mid+1, right),因为 mid 已经搜索过,应该从搜索区间中去除。


在这里插入图片描述


一文带你搞定二分搜索及多个变种

labuladong的算法专栏

算法实例

完全有序

查找某位数字位置
查找第一个 Or 最后一个出现的某个元素(该元素存在 Or 不存在);

旋转数组

寻找最小值(迭代的寻找无序的一边)
查找目标元素(是否重复)

package com.company.Sort;

public class BinarySearch {

    public static void main(String[] args) {
        BinarySearch binarySearch = new BinarySearch();
        int[] arr = {1,2,5,7,8,9};
        int[] arr_cyc = {9,10,1,2,5,7,8};
        // System.out.println(binarySearch.binary(arr,3));
        // System.out.println(binarySearch.binSearchWithTarget(arr_cyc, 7));
        // System.out.println(binarySearch.search(arr_cyc, 11));
        int[] arr_cycrep =  {2,5,6,0,0,1,2};
        System.out.println(binarySearch.binSearchWithTargetAndRepeat(arr_cycrep, 2));
    }

    /***
     * 旋转数组中 目标元素 的下标(位置)
     * 重复元素
     * */
    public int binSearchWithTargetAndRepeat(int[] nums, int target){
        int start = 0, end = nums.length-1;
        while(start <= end)
        {
            int mid = start + ((end - start) >>> 1);
            // 1. 判断是否有序
            if(target ==  nums[mid]) {                              // 中间 [mid, mid]
                return mid;
            }
            if(nums[mid] == nums[start] )
            {
                start++;
                continue;
            }
            if(nums[mid] == nums[end])
            {
                end--;
                continue;
            }
            if( nums[mid] <= nums[end]) {             // 右边有序 (mid, end] ,左边无序
                if(target > nums[mid] && target <= nums[end] )          // target 在有序区间内(右边)
                {
                    start = mid+1;
                }else {//if(target < nums[mid] || target > nums[end]){                                                              // target 在有序区间外(左边)
                    end = mid -1;
                }
            }else if(nums[mid] > nums[end]){              // 右边无序 (mid, end] ,左边有序
                if(target >= nums[start] && target < nums[mid] ){               // target 在无序区间内(右边)
                    end = mid-1;
                }else {//if(target < nums[start] || target > nums[mid]){
                    start = mid + 1;                                        // target 在无序区间外(左边)
                }
            }

        }
        return -1;
    }

    /***
     * 旋转数组中 目标元素 的下标(位置)
     * 无重复元素
     * */
    public int binSearchWithTarget(int[] nums, int target){
        int start = 0, end = nums.length-1;
        while(start <= end)
        {
            int mid = start + ((end - start) >>> 1);
            // 1. 判断是否有序
            if(target ==  nums[mid]) {                              // 中间 [mid, mid]
                return mid;
            }
            else if( nums[mid] <= nums[end]) {             // 右边有序 (mid, end] ,左边无序
                if(target > nums[mid] && target <= nums[end] )          // target 在有序区间内(右边)
                {
                    start = mid+1;
                }else {//if(target < nums[mid] || target > nums[end]){                                                              // target 在有序区间外(左边)
                    end = mid -1;
                }
            }else if(nums[mid] > nums[end]){              // 右边无序 (mid, end] ,左边有序
                if(target >= nums[start] && target < nums[mid] ){               // target 在无序区间内(右边)
                    end = mid-1;
                }else {//if(target < nums[start] || target > nums[mid]){
                    start = mid + 1;                                        // target 在无序区间外(左边)
                }
            }

        }
        return -1;
    }

    public int binSearchWithTarget2(int[] nums, int target){
        int start = 0, end = nums.length-1;
        while(start <= end)
        {
            int mid = start + ((end - start) >>> 1);
            // 1. 判断是否有序
            if(target ==  nums[mid]) {                              // 中间 [mid, mid]
                return mid;
            }
            else if( nums[mid] < nums[nums.length-1]) {             // 右边有序 (mid, end] ,左边无序
                if(target > nums[mid] && target <= nums[nums.length-1] )          // target 在有序区间内(右边)
                {
                    start = mid+1;
                }else{                                                              // target 在有序区间外(左边)
                    end = mid -1;
                }
            }else if(nums[mid] > nums[nums.length-1]){              // 右边无序 (mid, end] ,左边有序
                if(target >= nums[0] && target < nums[mid] ){               // target 在无序区间内(右边)
                    end = mid-1;
                }else{
                    start = mid + 1;                                        // target 在无序区间外(左边)
                }
            }

        }
        return -1;
    }

    /***
     * 旋转数组中最小元素的下标(位置)
     * */
    public int binSearchWithMin(int[] nums){
        int start = 0, end = nums.length-1;
        while(start < end)
        {
            // ------------------------------------
            // 优化代码
            if(nums[start] <= nums[end])
                return start;
            // ------------------------------------
            int mid = start + ((end - start) >>> 1);
            if(nums[mid] <= nums[end])
                end = mid;
            else{
                start = mid+1;
            }
        }
        return start;
    }
    /**
     * 存在重复元素的情况:
     *      查找第一次 Or 最后一次 出现的目标元素索引;
     *      或者说: 获取小于 / 大于 目标元素的数目
     * */
    public int binSearchWithRepeat (int[] nums, int target) {
        // write code here
        if(nums == null || nums.length == 0)
            return -1;
        int start = 0, end = nums.length-1;
        while(start < end)
        {
            int mid = start + ((end - start) >>> 1);
            if(target <= nums[mid])
                end = mid;
            else if(nums[mid] < target)
                start = mid+1;
        }
        System.out.println(start +" "+ nums[start]+" "+ end);
        return nums[start] == target ? start : -1;

    }
    /**
     * 无重复元素: 普通二分
     * */
    public int binSearch(int[] arr, int target) {
        int min = 0;
        int max = arr.length - 1;		// 1. max 的取值
        int mid;
        while (min <= max) {			// 2. while的判断
            mid = (min + max) / 2; 		// 3. 中间值的计算
            if (arr[mid] > target) {
                max = mid - 1;  		// 4. min 下一个值的变化
            } else if (arr[mid] < target) {
                min = mid + 1;			// 5. max 下一个值的变化
            } else {
                return mid;				// 6. 返回值的情况
            }
        }
        return -1;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值