力扣35搜索插入位置:思路分析+图文详解+代码实现+拓展java源码

简介: 力扣35搜索插入位置:思路分析+图文详解+代码实现+拓展java源码

第一部分:题目描述

🏠 链接:35. 搜索插入位置 - 力扣(LeetCode)

⭐ 难度:简单

第二部分:思路分析

我们可以先看下普通二分查找的代码:满足了查到返回索引,查不到返回-1

public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int mid;
        while (left <= right) {
            // 得到中间索引
            /*
                考虑到 left+right 的值可能会超过 int可表示 的最大值,我们不再对他们的和直接除以2
                我们知道 除以2 的操作可以用 位运算 >>1 来代替
                但还不够,由于 (left+right) 值溢出表示负数,>>1 只是做 除以2 操作,最高位符号位不变,依旧为1表示负数,负数除以2依旧是负数
                这时候我们可以修改为 无符号右移 >>>1 ,低位溢出,高位补0,那么最高位符号位为0就表示正数了
             */
            mid = (left + right) >>> 1;
            if (target < nums[mid]) {
                // 如果目标值小于中间值
                right = mid - 1;
            } else if (nums[mid] < target) {
                // 如果目标值大于中间值
                left = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }

而这道题目的要求是:查到返回索引,查不到返回应该插入位置的索引。

我们可以这样来分析:

  1. 如果能够查询得到,还是走原来的逻辑,即 else 块中的 return mid
  2. 如果查询不到,你首先必须肯定这样一件事:
  • 全部搜索完还查找不到时 while 循环会退出
  • 具体是为什么会退出呢?==》left <= right 的条件不成立,即 left > right,更具体的说是 right + 1 == left 了。

  1. 紧接着,我们又必须赞成一件事:
  • 在 left > right ,即 right + 1 == left 之前的while循环中,肯定有 left == right 成立

  • 在以 left == right 为循环条件的循环体中,进行了 if 语句的right指针向左移动 或者 else-if 语句的left指针向右移动,而导致出现了 right + 1 == left ,自此循环退出。
  1. 那么这个时候就很好分析了,我们知道了最后一次循环时的循环条件是left == right,至于是if 语句的right指针向左移动还是else-if 语句的left指针向右移动都有可能,我们分这两种情况进行分析:
  • if 语句的right指针向左移动

    当目标值小于 中间索引mid对应值 时,会走 if 语句导致 right 指针左移,自此 while 循环会结束。

    对于是一个从小到大排序的升序数组,我们知道插入位置应该放在 中间索引mid 的前面,即插入位置应当就是 当前中间索引mid ,那不就是 left指针 的位置吗?
  • else-if 语句的left指针向右移动

    当目标值大于 中间索引mid对应值 时,会走 else-if 语句导致 left 指针右移,自此 while 循环会结束。

    对于是一个从小到大排序的升序数组,我们知道插入位置应该放在 中间索引mid 的后面,而最后一次循环操作后 left 指针从原来的 mid 位置右移了一位,那插入位置不就是最终 left指针 的位置吗?
  1. 因此,综上所述
  • 查找到了,就是原二分查找的 return mid
  • 未查找到,就是最终 left指针 的位置。 因此只需要将 return -1 改为 return left 即可。

第三部分:代码实现

public int searchInsert(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int mid;
        while (left <= right) {
            // 得到中间索引
            /*
                考虑到 left+right 的值可能会超过 int可表示 的最大值,我们不再对他们的和直接除以2
                我们知道 除以2 的操作可以用 位运算 >>1 来代替
                但还不够,由于 (left+right) 值溢出表示负数,>>1 只是做 除以2 操作,最高位符号位不变,依旧为1表示负数,负数除以2依旧是负数
                这时候我们可以修改为 无符号右移 >>>1 ,低位溢出,高位补0,那么最高位符号位为0就表示正数了
             */
            mid = (left + right) >>> 1;
            if (target < nums[mid]) {
                // 如果目标值小于中间值
                right = mid - 1;
            } else if (nums[mid] < target) {
                // 如果目标值大于中间值
                left = mid + 1;
            } else {
                return mid;
            }
        }
        return left;
    }

第四部分:拓展-Java底层源码对二分查找的实现

⭐ 在 Arrays 类中的方法 binarySearch0(int[] a, int fromIndex, int toIndex, int key),源码如下:

/**
     * 使用二进制搜索算法在指定的整数数组中搜索指定的值。在进行此调用之前,必须对数组进行排序(按方法排序 sort(int[]) )。
     * 如果未排序,则结果未定义。如果数组包含多个具有指定值的元素,则无法保证会找到哪个元素。
     * 参数:
   *    a – 要搜索的数组 
   *    key – 要搜索的值
   * 返回:搜索键的索引(如果它包含在数组中);否则,( -(插入点)-1)。
   * 插入点定义为将键插入数组的 点 :第一个元素的索引大于键,如果数组中的所有元素都小于指定的键,则为 a.length 。
   * 请注意,这保证了当且仅当找到键时返回值将为 >= 0。
     */
    public static int binarySearch(int[] a, int key) {
        return binarySearch0(a, 0, a.length, key);
    }
    // Like public version, but without range checks.
    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.
    }

第五部分:拓展-利用Arrays实现二分查找目标值,不存在则插入

public static void main(String[] args) {
        // 二分查找目标值,不存在则插入
        /*
            原始数组:[2,5,8]
            查找目标值:4
            查询不到,返回的结果为 r = -待插入点索引-1
            在这里带插入点索引为 1,对应 r = -2
            那么我们分成这几步来进行拷贝:
                - 1.新建数组,大小为原数组的大小+1:         [0,0,0,0]
                - 2.将待插入点索引之前的数据放入新数组:     [2,0,0,0]
                - 3.将目标值放入到待插入点索引的位置:       [2,4,0,0]
                - 4.将原数组后面的数据都相继拷贝到新数组后面: [2,4,5,8]
         */
        // 定义原数组与目标值
        int[] oldArray = {2, 5, 8};
        int target = 4;
        // 搜索目标值4,没有找到,返回结果为 r =  -待插入点索引-1,这里的 r=-2
        int r = Arrays.binarySearch(oldArray, target);
        // r < 0 说明没有找到目标值,就插入
        if (r < 0) {
            // 获取待插入索引
            int insertIndex = -r - 1;
            // 1.新建数组,大小为原数组的大小+1
            int[] newArray = new int[oldArray.length + 1];
            // 2.将待插入点索引之前的数据放入新数组
            System.arraycopy(oldArray, 0, newArray, 0, insertIndex);
            // 3.将目标值放入到待插入点索引的位置
            newArray[insertIndex] = target;
            // 4.将原数组后面的数据都相继拷贝到新数组后面
            System.arraycopy(oldArray, insertIndex, newArray, insertIndex + 1, oldArray.length - insertIndex);
            System.out.println(Arrays.toString(newArray));
        }
    }

第六部分:拓展-(left + right) >>> 1的分析

在本文中我使用的是 (left + right) >>> 1 来代替 (left + right) / 2,目的是解决 left + right 超过int最大值 的问题。

我们先来举个模拟问题的发生:

public static void main(String[] args) {
        // 模拟 二分查找中的 left
        int left = 100;
        // 模拟 二分查找中的 right
        int right = Integer.MAX_VALUE - 1;
        // 此时 left+right 的值超过了 int范围 的最大值,导致 left + right 的结果为负数
        // 然后对负数进行除以2操作,结果依旧为负数
        int mid = (left + right) / 2;
        // 输出结果为 -1073741775
        System.out.println(mid);
    }

那如何解决这个问题呢?我们可以使用 位运算 来代替 /2 的操作。

  • 算数右移 >> :低位溢出,符号位不变,并用符号位补溢出的高位。
  • 逻辑右击(无符号右移)>>>:低位溢出,高位补0。

由于最高位符号位为0表示该数为正数,因此相比于 >> 做到了能将一个 负数 无符号右移后变成 正数。

第七部分:方法-返回≥目标的最靠左索引

除了使用第三部分的代码实现方法,还可以使用 Leftmost 方式解决该问题:

具体解释参考:力扣704二分查找:思路分析+代码实现(递归与非递归)_是谢添啊的博客-CSDN博客

public int searchInsert(int[] array, int target) {
        int left = 0;
        int right = array.length - 1;
        int mid;
        while (left <= right) {
            mid = (left + right) >>> 1;
            if (target <= array[mid]) {
                // array[mid] 满足大于等于目标值,因此可以记录
                right = mid - 1;
            } else if (array[mid] < target) {
                // 目标值大于中间索引值,缩小左范围
                left = mid + 1;
            }
        }
        // 返回结果
        return left;
    }


相关文章
|
1月前
|
Java Go 开发工具
【Java】(9)抽象类、接口、内部的运用与作用分析,枚举类型的使用
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类abstract static不能同时修饰一个方法。
200 1
|
1月前
|
存储 Java Go
【Java】(3)8种基本数据类型的分析、数据类型转换规则、转义字符的列举
牢记类型转换规则在脑海中将编译和运行两个阶段分开,这是两个不同的阶段,不要弄混!
186 2
|
2月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
|
3月前
|
安全 Java 编译器
new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析
逃逸分析是一种静态程序分析技术,用于判断对象的可见性与生命周期。它帮助即时编译器优化内存使用、降低同步开销。根据对象是否逃逸出方法或线程,分析结果分为未逃逸、方法逃逸和线程逃逸三种。基于分析结果,编译器可进行同步锁消除、标量替换和栈上分配等优化,从而提升程序性能。尽管逃逸分析计算复杂度较高,但其在热点代码中的应用为Java虚拟机带来了显著的优化效果。
125 4
|
3月前
|
机器学习/深度学习 安全 Java
Java 大视界 -- Java 大数据在智能金融反洗钱监测与交易异常分析中的应用(224)
本文探讨 Java 大数据在智能金融反洗钱监测与交易异常分析中的应用,介绍其在数据处理、机器学习建模、实战案例及安全隐私等方面的技术方案与挑战,展现 Java 在金融风控中的强大能力。
|
4月前
|
存储 Java 大数据
Java 大视界 -- Java 大数据在智能家居能源消耗模式分析与节能策略制定中的应用(198)
简介:本文探讨Java大数据技术在智能家居能源消耗分析与节能策略中的应用。通过数据采集、存储与智能分析,构建能耗模型,挖掘用电模式,制定设备调度策略,实现节能目标。结合实际案例,展示Java大数据在智能家居节能中的关键作用。
|
5月前
|
数据采集 搜索推荐 算法
Java 大视界 -- Java 大数据在智能教育学习社区用户互动分析与社区活跃度提升中的应用(274)
本文系统阐述 Java 大数据技术在智能教育学习社区中的深度应用,涵盖数据采集架构、核心分析算法、活跃度提升策略及前沿技术探索,为教育数字化转型提供完整技术解决方案。
|
5月前
|
Java 数据库连接 API
互联网大厂校招 JAVA 工程师笔试题解析及常见考点分析
本文深入解析互联网大厂校招Java工程师笔试题,涵盖基础知识(数据类型、流程控制)、面向对象编程(类与对象、继承与多态)、数据结构与算法(数组、链表、排序算法)、异常处理、集合框架、Java 8+新特性(Lambda表达式、Stream API)、多线程与并发、IO与NIO、数据库操作(JDBC、ORM框架MyBatis)及Spring框架基础(IoC、DI、AOP)。通过技术方案讲解与实例演示,助你掌握核心考点,提升解题能力。
236 2
|
传感器 分布式计算 安全
Java 大视界 -- Java 大数据在智能安防入侵检测系统中的多源数据融合与分析技术(171)
本文围绕 Java 大数据在智能安防入侵检测系统中的应用展开,剖析系统现状与挑战,阐释多源数据融合及分析技术,结合案例与代码给出实操方案,提升入侵检测效能。
|
6月前
|
缓存 安全 Java
【高薪程序员必看】万字长文拆解Java并发编程!(3-1):并发共享问题的解决与分析
活锁:多个线程相互影响对方退出同步代码块的条件而导致线程一直运行的情况。例如,线程1的退出条件是count=5,而线程2和线程3在其代码块中不断地是count进行自增自减的操作,导致线程1永远运行。内存一致性问题:由于JIT即时编译器对缓存的优化和指令重排等造成的内存可见性和有序性问题,可以通过synchronized,volatile,并发集合类等机制来解决。这里的线程安全是指,多个线程调用它们同一个实例的方法时,是线程安全的,但仅仅能保证当前调用的方法是线程安全的,不同方法之间是线程不安全的。
132 0

热门文章

最新文章