java数据结构与算法之二分查找(蓝桥杯)

二分查找算法也称折半查找,是一种非常高效的工作于有序数组的查找算法。后续的课程中还会学习更多的查找算法,但在此之前,不妨用它作为入门。

1.基础版

需求:在有序数组 A 内,查找值 target

  • 如果找到返回索引

  • 如果找不到返回 -1

算法描述

  • 对于一个算法来讲,都有较为严谨的描述,上面是一个例子

  • 后续讲解时,以简明直白为目标,不会总以上面的方式来描述算法

java 复制代码
package demo;

/**
 * 找的到返回索引
 * 找不到返回-1
 */
public class Test1 {
    //查找元素5的下标
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
        int index = binarySearch(arr, 9);
        System.out.println(index);

    }

    private static int binarySearch(int[] arr, int number) {
        int left = 0;//左下标
        int right = arr.length - 1;//右下标
        while (left <= right) {
            // int mid = (left + right) / 2;//中间值
            //解决二个正整数相加/2不出现负数(溢出)才进行右移运算
            /*因为正整数相加/2不能动符号位,移位可以进行符号位,右移1位相当于除以2(向下取整)
            * */
            int mid = (left + right) >>> 1;//右移1位,相当于除以2(向下取整)
            if (arr[mid] == number) {
                return mid;
            } else if (arr[mid] > number) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return -1;
    }
}
  • i,j 对应着搜索区间 [0,arr.length-1](注意是闭合的区间),left<=right 意味着搜索区间内还有未比较的元素,left,right 指向的元素也可能是比较的目标

    • 思考:如果不加left==right行不行?

      • 回答:不行,因为这意味着 left,right 指向的元素会漏过比较
  • m 对应着中间位置,中间位置左边和右边的元素可能不相等(差一个),不会影响结果

  • 如果某次未找到,那么缩小后的区间内不包含 mid

复制代码
// int mid = (left + right) / 2;//中间值
//解决二个正整数相加/2不出现负数(溢出)才进行右移运算
/*因为正整数相加/2不能动符号位,移位可以进行符号位,右移1位相当于除以2(向下取整)
* */
int mid = (left + right) >>> 1;//右移1位,相当于除以2(向下取整)

2.改变版

cpp 复制代码
public static int binarySearch(int[] a, int target) {
    int i = 0, j = a.length;
    while (i < j) {
        int m = (i + j) >>> 1;
        if (target < a[m]) {			// 在左边
            j = m;
        } else if (a[m] < target) {		// 在右边
            i = m + 1;
        } else {
            return m;
        }
    }
    return -1;
}
  • i,j 对应着搜索区间 [0,a.length)(注意是左闭右开的区间),i<j 意味着搜索区间内还有未比较的元素,j 指向的一定不是查找目标

    • 思考:为啥这次不加 i==j 的条件了?

    • 回答:这回 j 指向的不是查找目标,如果还加 i==j 条件,就意味着 j 指向的还会再次比较,找不到时,会死循环

  • 如果某次要缩小右边界,那么 j=m,因为此时的 m 已经不是查找目标了

为什么是j=m呢?

因为j最开始是指向不存在的元素的,如果写成j=m-1 ,那就可能m-1是我们要找的元素,就可能导致漏查。

3.平衡版

cpp 复制代码
public static int binarySearchBalance(int[] a, int target) {
    int i = 0, j = a.length;
    while (1 < j - i) {
        int m = (i + j) >>> 1;
        if (target < a[m]) {
            j = m;
        } else {
            i = m;
        }
    }
    return (a[i] == target) ? i : -1;
}

思想:

  1. 左闭右开的区间,i 指向的可能是目标,而 j 指向的不是目标

  2. 不奢望循环内通过 m 找出目标, 缩小区间直至剩 1 个, 剩下的这个可能就是要找的(通过 i)

    • j - i > 1 的含义是,在范围内待比较的元素个数 > 1
  3. 改变 i 边界时,它指向的可能是目标,因此不能 m+1

  4. 循环内的平均比较次数减少了

4.Java 版

cpp 复制代码
private static int binarySearch0(long[] a, int fromIndex, int toIndex,
                                     long key) {
    int low = fromIndex;
    int high = toIndex - 1;

    while (low <= high) {
        int mid = (low + high) >>> 1;
        long midVal = a[mid];

        if (midVal < key)
            low = mid + 1;
        else if (midVal > key)
            high = mid - 1;
        else
            return mid; // key found
    }
    return -(low + 1);  // key not found.
}

long[] a 目标数组

int fromIndex为起始索引

int toIndex结束索引

long key代表要查找的目标元素值

  • 例如 [1,3,5,6] 要插入 2 那么就是找到一个位置,这个位置左侧元素都比它小

    • 等循环结束,若没找到,low 左侧元素肯定都比 target 小,因此 low 即插入点
  • 插入点取负是为了与找到情况区分

  • -1 是为了把索引 0 位置的插入点与找到的情况进行区分

5.Leftmost与 Rightmost

有时我们希望返回的是最左侧的重复元素,如果用 Basic 二分查找

  • 对于数组 [1, 2, 3, 4, 4, 5, 6, 7],查找元素4,结果是索引3

  • 对于数组 [1, 2, 4, 4, 4, 5, 6, 7],查找元素4,结果也是索引3,并不是最左侧的元素

cpp 复制代码
public static int binarySearchLeftmost1(int[] a, int target) {
    int i = 0, j = a.length - 1;
    int candidate = -1;
    while (i <= j) {
        int m = (i + j) >>> 1;
        if (target < a[m]) {
            j = m - 1;
        } else if (a[m] < target) {
            i = m + 1;
        } else {
            candidate = m; // 记录候选位置
            j = m - 1;     // 继续向左
        }
    }
    return candidate;
}

有多个目标值时,采用candidate是记录索引的中间变量解决

如果希望返回的是最右侧元素

cpp 复制代码
public static int binarySearchRightmost1(int[] a, int target) {
    int i = 0, j = a.length - 1;
    int candidate = -1;
    while (i <= j) {
        int m = (i + j) >>> 1;
        if (target < a[m]) {
            j = m - 1;
        } else if (a[m] < target) {
            i = m + 1;
        } else {
            candidate = m; // 记录候选位置
            i = m + 1;	   // 继续向右
        }
    }
    return candidate;
}

应用

对于 Leftmost 与 Rightmost,可以返回一个比 -1 更有用的值

Leftmost 改为

cpp 复制代码
public static int binarySearchLeftmost(int[] a, int target) {
    int i = 0, j = a.length - 1;
    while (i <= j) {
        int m = (i + j) >>> 1;
        if (target <= a[m]) {
            j = m - 1;
        } else {
            i = m + 1;
        }
    }
    return i; 
}

返回i的含义是>=target最靠左的索引

Rightmost 改为

cpp 复制代码
public static int binarySearchRightmost(int[] a, int target) {
    int i = 0, j = a.length - 1;
    while (i <= j) {
        int m = (i + j) >>> 1;
        if (target < a[m]) {
            j = m - 1;
        } else {
            i = m + 1;
        }
    }
    return i - 1;
}

返回i-1的含义是<=target最靠右的索引

搜索插入位置-Leetcode 35

参考答案1:用二分查找基础版代码改写,基础版中,找到返回 m,没找到 i 代表插入点,因此有

34

cs 复制代码
class Solution {
    public int[] searchRange(int[] a, int target) {

        int x=left(a,target);
        if(x==-1){
            return new int[]{-1,-1};

        }else{
            return new int[]{x,right(a,target)};
        }
    }

        public int left(int[]a,int target){
            int i=0;
            int j=a.length-1;
            int tmp=-1;
            while(i<=j){
                int m=(i+j)>>>1;
                if(a[m]>target){
                    j=m-1;
                }else if(a[m]<target){
                    i=m+1;
                }else{
                    tmp=m;
                    j=m-1;

                }
            }
            return tmp;
        }
        public int right(int[]a,int target){
            int i=0;
            int j=a.length-1;
            int tmp=-1;
            while(i<=j){
                int m=(i+j)>>>1;
                if(a[m]>target){
                    j=m-1;
                }else if(a[m]<target){
                    i=m+1;
                }else{
                    tmp=m;
                  i=m+1;

                }
            }
            return tmp;
        }
    }
相关推荐
dengjiayue6 分钟前
golang实现简单的reids服务2
开发语言·golang
西岭千秋雪_8 分钟前
设计模式の单例&工厂&原型模式
java·单例模式·设计模式·简单工厂模式·工厂方法模式·抽象工厂模式·原型模式
fanchael_kui21 分钟前
使用elasticsearch-java客户端API生成DSL语句
java·大数据·elasticsearch
m0_7482565623 分钟前
[CTF夺旗赛] CTFshow Web1-14 详细过程保姆级教程~
java
疯狂的沙粒29 分钟前
JavaScript 单例模式的创建与应用
开发语言·前端·javascript·vue.js
余额不足1213832 分钟前
C语言基础六:循环结构及面试上机题
c语言·开发语言
T.O.P1132 分钟前
Spring&SpringBoot常用注解
java·spring boot·spring
hn小菜鸡1 小时前
LeetCode 2320.统计放置房子的方式数
算法·leetcode·职场和发展
高一学习c++会秃头吗1 小时前
leetcode_547 省份数量
算法·leetcode·职场和发展
天赐细莲1 小时前
(仓颉) Cangjie 刷力扣基础语法小结
数据结构·算法·leetcode·职场和发展