数据结构和算法之【二分查找】

目录

二分查找法

需求

具体实现

LeetCode-704题

题解

测试结果

时间复杂度

常见的几种时间复杂度

二分查找法的时间复杂度

Java中的二分查找

查看源码

分析源码上的注释

拓展

需求实现

测试结果

LeetCode-35题

题解

方法一

方法二

测试结果

方法一

方法二

LeetCode-34题

题解

测试结果


二分查找法

需求

给定一个升序数组upSortArray,查找目标值target

1)、找到目标值,返回其索引

2)、未找到目标值,返回-1

具体实现

java 复制代码
public static int binarySearch(int[] upSortArray, int target) {
    // 参数不合法
    if (Objects.isNull(upSortArray) || upSortArray.length == 0) {
        throw new IllegalArgumentException("非法参数");
    }
    // 定义low和high指针(首尾指针)
    int low = 0;
    int high = upSortArray.length - 1;
    // 只要low指针不大于high指针,就一直缩小范围比较
    while (low <= high) {
        // 取中间索引
        int mid = (low + high) >>> 1;
        // 中间索引位置的值
        int midVal = upSortArray[mid];
        if (midVal < target) {// 目标值比中间值大
            // 将low指针置于中间位置后面的索引(舍弃中间值左边的所有元素)
            low = mid + 1;
        } else if (target < midVal) {// 目标值比中间值小
            // 将high指针置于中间位置前面的索引(舍弃中间值右边的所有元素)
            high = mid - 1;
        } else {// 目标值等于中间值
            // 找到了目标值target,返回索引
            return mid;
        }
    }
    // 未找到目标值,返回-1
    return -1;
}

LeetCode-704题

题解

java 复制代码
class Solution {
    public int search(int[] nums, int target) {
        // 参数不合法
        if (Objects.isNull(nums) || nums.length == 0) {
            throw new IllegalArgumentException("非法参数");
        }
        // 定义low和high指针(首尾指针)
        int low = 0;
        int high = nums.length - 1;
        // 只要low指针不大于high指针,就一直缩小范围比较
        while (low <= high) {
            // 取中间索引
            int mid = (low + high) >>> 1;
            // 中间索引位置的值
            int midVal = nums[mid];
            if (midVal < target) {// 目标值比中间值大
                // 将low指针置于中间位置后面的索引(舍弃中间值左边的所有元素)
                low = mid + 1;
            } else if (target < midVal) {// 目标值比中间值小
                // 将high指针置于中间位置前面的索引(舍弃中间值右边的所有元素)
                high = mid - 1;
            } else {// 目标值等于中间值
                // 找到了目标值target,返回索引
                return mid;
            }
        }
        // 未找到目标值,返回-1
        return -1;
    }
}

测试结果

时间复杂度

常见的几种时间复杂度

按照时间复杂度从好到差依次排列如下

  • O(1):常量时间,算法时间不会随着数据规模而改变
  • O(log(n)):对数时间
  • O(n):线性时间,算法时间和数据规模成正比
  • O(n*log(n)):拟线性时间
  • O(n^2):平方时间
  • O(2^n):指数时间
  • O(n!):阶乘时间

二分查找法的时间复杂度

O(log(n))对数时间

Java中的二分查找

查看源码

Java中Arrays类的binarySearch方法有很多重载形式,这里查看参数类型为int的方法

java 复制代码
package java.util;

// ...

public class Arrays {
    // ...

    public static int binarySearch(int[] a, int key) {
        return binarySearch0(a, 0, a.length, key);
    }

    private static int binarySearch0(int[] a, int fromIndex, int toIndex,
                                     int key) {
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            int 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.
    }

    // ...
}

分析源码上的注释

这里只分析部分有关返回值的注释,大概意思如下

  • 如果搜索的值包含在数组中,则返回其索引
  • 否则,返回(-(插入点)-1)
java 复制代码
/**
 * @return index of the search key, if it is contained in the array; 
 *         otherwise, (-(insertion point) - 1).
 */
public static int binarySearch(int[] a, int key) {
    return binarySearch0(a, 0, a.length, key);
}

拓展

通过注释的分析可知,low即为插入点(因为返回值为-(low+1)),这里可以做一下扩展,当知道插入点后,就可以实现需求:如果目标值不存在,则将目标值插入数组中,保持原来数组的顺序

需求实现

java 复制代码
package algorithm.search;

import java.util.Arrays;
import java.util.Objects;

public class Expand {
    public static void main(String[] args) {
        // 给定一个升序数组upSortArray和一个目标值target
        int[] upSortArray = {12, 20, 22, 34, 36, 40, 43, 46, 46, 55, 60, 78, 80, 83, 83, 99};
        int target = 40; // 这里可以分别定义一些存在和不存在的target看测试结果
        //int target = 10;
        //int target = 66;
        //int target = 100;
        // 获取返回值
        int resultVal = Arrays.binarySearch(upSortArray, target);
        // 如果返回值为非负数,为target目标值在数组upSortArray中的索引位置
        if (resultVal >= 0) {
            System.out.println("目标值[" + target + "]存在于数组中,索引位置为:" + resultVal);
            return;
        }
        System.out.println("数组中不包含目标值:[" + target + "],现将目标值插入数组中...");
        // 如果返回值为负数,为(-(插入点)-1)
        int insertPoint = Math.abs(resultVal + 1);// 这里通过(+1然后取绝对值)来算出插入点
        System.out.println("老数组:" + Arrays.toString(upSortArray));
        // 计算出插入点,将target目标值插入正确的位置,形成一个新数组
        upSortArray = handleInsertTargetToArray(upSortArray, target, insertPoint);
        System.out.println("新数组:" + Arrays.toString(upSortArray));
    }

    /**
     * 处理插入目标值,返回新数组
     */
    public static int[] handleInsertTargetToArray(int[] array, int target, int insertPoint) {
        // 参数校验
        if (Objects.isNull(array)) throw new IllegalArgumentException("数组不合法");
        if (insertPoint < 0) throw new IllegalArgumentException("插入点不合法");
        // 新数组长度
        int newArrayLen = array.length + 1;
        // 定义新数组
        int[] newArray = new int[newArrayLen];
        // 将插入点之前的数据拷贝到新数组中
        System.arraycopy(array, 0, newArray, 0, insertPoint);
        // 将目标值放入新数组中
        newArray[insertPoint] = target;
        // 将插入点之后的数据拷贝到新数组中
        System.arraycopy(array, insertPoint, newArray, (insertPoint + 1), (array.length - insertPoint));
        return newArray;
    }
}

测试结果

  • target = 40
bash 复制代码
目标值[40]存在于数组中,索引位置为:5
  • target = 10
bash 复制代码
数组中不包含目标值:[10],现将目标值插入数组中...
老数组:[12, 20, 22, 34, 36, 40, 43, 46, 46, 55, 60, 78, 80, 83, 83, 99]
新数组:[10, 12, 20, 22, 34, 36, 40, 43, 46, 46, 55, 60, 78, 80, 83, 83, 99]
  • target = 66
bash 复制代码
数组中不包含目标值:[66],现将目标值插入数组中...
老数组:[12, 20, 22, 34, 36, 40, 43, 46, 46, 55, 60, 78, 80, 83, 83, 99]
新数组:[12, 20, 22, 34, 36, 40, 43, 46, 46, 55, 60, 66, 78, 80, 83, 83, 99]
  • target = 100
bash 复制代码
数组中不包含目标值:[100],现将目标值插入数组中...
老数组:[12, 20, 22, 34, 36, 40, 43, 46, 46, 55, 60, 78, 80, 83, 83, 99]
新数组:[12, 20, 22, 34, 36, 40, 43, 46, 46, 55, 60, 78, 80, 83, 83, 99, 100]

LeetCode-35题

题解

方法一

可以使用Java中提供的二分查找来实现这道题,只需要做小小的改动

java 复制代码
class Solution {
    public int searchInsert(int[] nums, int target) {
        return binarySearch0(nums, 0, nums.length, target);
    }

    // 这里基本上完全用的是Java中提供的二分查找算法,只是改动了最后一行
    private static int binarySearch0(int[] a, int fromIndex, int toIndex,
            int key) {
        int low = fromIndex;
        int high = toIndex - 1;

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

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

        // Java中这里返回的是-(插入点 + 1)
        // return -(low + 1);  // key not found.

        // 本题需要返回的是插入点即可
        return low;
    }
}

方法二

使用二分查找法的LeftMost (如数组中存在一些重复的target值,通过LeftMost算法可以找到最左侧的target值所在的索引)

java 复制代码
class Solution {
    public int searchInsert(int[] nums, int target) {
        int low = 0;
        int high = nums.length - 1;

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

            // 这里做出了改变,当遇到第一个target值时,继续像左找
            if (target <= midVal) {
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }

        return low;
    }
}

测试结果

方法一

方法二

LeetCode-34题

题解

java 复制代码
class Solution {
    public int[] searchRange(int[] nums, int target) {
        // 参数校验
        if (Objects.isNull(nums) || nums.length == 0) {
            return buildNoTargetResult();
        }
        // 初始化返回结果
        int[] result = new int[2];
        // 获取数组中最左侧的target值所在下标
        int leftMost = targetIndexLeftMost(nums, target);
        // 数组中不存在target值
        if (leftMost < 0) {
            return buildNoTargetResult();
        }
        // 设置返回结果的左边界
        result[0] = leftMost;
        // 获取数组中最右侧的target值所在下标
        int rightMost = targetIndexRightMost(nums, target, leftMost + 1);
        // 最左侧之后再无target值出现
        if (rightMost < 0) {
            result[1] = leftMost;
            return result;
        }
        // 设置返回结果的右边界
        result[1] = rightMost;
        return result;
    }

    /**
     * 数组中无目标值
     */
    public int[] buildNoTargetResult() {
        return new int[] { -1, -1 };
    }

    /**
     * target值出现在数组中最左侧的index
     */
    public int targetIndexLeftMost(int[] nums, int target) {
        int low = 0;
        int high = nums.length - 1;
        // 记录target在数组中的最左侧下标
        int leftMost = -1;
        while (low <= high) {
            int mid = (low + high) >>> 1;
            int midVal = nums[mid];
            if (target < midVal) {
                high = mid - 1;
            } else if (midVal < target) {
                low = mid + 1;
            } else {
                // 更新最左下标
                leftMost = mid;
                // 这里不停止寻找,继续向左寻找是否还有目标值
                high = mid - 1;
            }
        }
        return leftMost;
    }

    /**
     * target值出现在数组中最右侧的index
     */
    public int targetIndexRightMost(int[] nums, int target, int low) {
        // 目标值最左侧为数组最后一个元素
        if (low == nums.length) {
            return -1;
        }
        int high = nums.length - 1;
        // 记录target在数组中的最左侧下标
        int rightMost = -1;
        while (low <= high) {
            int mid = (low + high) >>> 1;
            int midVal = nums[mid];
            if (target < midVal) {
                high = mid - 1;
            } else if (midVal < target) {
                low = mid + 1;
            } else {
                // 更新最右下标
                rightMost = mid;
                // 这里不停止寻找,继续向右寻找是否还有目标值
                low = mid + 1;
            }
        }
        return rightMost;
    }
}

测试结果

相关推荐
忡黑梨2 小时前
BUUCTF_reverse_[MRCTF2020]Transform
c语言·开发语言·数据结构·python·算法·网络安全
于先生吖2 小时前
Java 同城服务同城租房系统源码 完整项目实现
java·开发语言
与数据交流的路上2 小时前
oceanbase-长事务排查
java·数据库·oceanbase
ascarl20102 小时前
canal和ES同步失败维护步骤
java·数据库·elasticsearch
枳颜2 小时前
LeetCode 466:统计重复个数
数据结构·算法·字符串
TYFHVB122 小时前
2026六大主流CRM横评,五大核心维度深度解析
大数据·前端·数据结构·人工智能
爱和冰阔落2 小时前
【C++STL上】栈和队列模拟实现 容器适配器 力扣经典算法秘籍
数据结构·c++·算法·leetcode·广度优先
程序员-King.2 小时前
day162—递归—买卖股票的最佳时机Ⅱ(LeetCode-122)
算法·leetcode·深度优先·递归
Gorgous—l2 小时前
数据结构算法学习:LeetCode热题100-贪心算法篇(数组中的第K个最大元素、 前 K 个高频元素、数据流的中位数)
数据结构·学习·算法