二分查找与搜索树高频问题

news2024/11/19 7:22:32

关卡名

逢试必考的二分查找

我会了✔️



内容

1.山脉数组的峰顶索引

✔️

2.旋转数字的最小数字

✔️

3.寻找缺失数字

✔️

4.优化求平方根

✔️

5.中序与搜索树原理

✔️

6.二叉搜索树中搜索特定值

✔️

7.验证二叉搜索树

✔️

基于二分查找思想,可以拓展出很多算法问题的,而且很多都是考察的热门, 这里我们整理了几道经典的问题。
二分在算法中的应用非常多,也是很多大厂钟爱的考察类型,感兴趣的同学可以继续研究一下:

  1. CC150面试题53:在排序数组中查找数字,要求:统一几个数字在排序数组中出现的次数,例如,输入排序数组{1,2,3,3,3,3,4,5}和数字3,由于3在数组中出现了4次,因此输出4。
  2. leetcode 34. 在排序数组中查找元素的第一个和最后一个位置
  3. LeetCode875.爱吃香蕉的珂珂
  4. LeetCode29.两数相除

在前面我们发现很多题使用前序、后序或者层次遍历都可以解决,但几乎没有中序遍历的。这是因为中序与前后序相比有不一样的特征,例如中序可以和搜索树结合在一起,但是前后序则不行。
在理解了二分搜索之后,我们会发现中序搜索与二分查找简直就是一个娘养的,实在太像了。这里我们就来研究一下中序搜索的问题。 

1.基于二分查找的拓展问题 

1.1 山脉数组的峰顶索引

LeetCode852.这个题的要求有点啰嗦,核心意思就是在数组中的某位位置i开始,从0到i是递增的,从i+1 到数组最后是递减的,让你找到这个最高点。
详细要求是:符合下列属性的数组 arr 称为山脉数组 :arr.length >= 3存在 i(0 < i < arr.length - 1)使得:

  • arr[0] < arr[1] < ... arr[i-1] < arr[i]
  • arr[i] > arr[i+1] > ... > arr[arr.length - 1]

给你由整数组成的山脉数组 arr ,返回任何满足 arr[0] < arr[1] < ... arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[arr.length - 1] 的下标 i 。
这个题其实就是前面找最小值的相关过程而已,最简单的方式是对数组进行一次遍历。
当我们遍历到下标i时,如果有arr[i-1]<arr[i]>arr[i+1],那么i就是我们需要找出的下标。
其实还可以更简单一些,因为是从左开始找的,开始的时候必然是arr[i-1]<a[i],所以只要找到第一个arr[i]>arr[i+1]的位置即可。代码就是:

public int peakIndexInMountainArray(int[] arr) {
    int n = arr.length;
    int ans = -1;
    for (int i = 1; i < n - 1; ++i) {
        if (arr[i] > arr[i + 1]) {
            ans = i;
            break;
        }
    }
    return ans;
}

这个题能否使用二分来优化一下呢?当然可以。
对于二分的某一个位置 mid,mid 可能的位置有3种情况:

  • mid在上升阶段的时候,满足arr[mid]>a[mid-1] && arr[mid]<arr[mid+1]
  • mid在顶峰的时候,满足arr[i]>a[i-1] && arr[i]>arr[i+1]
  • mid在下降阶段,满足arr[mid]<a[mid-1] && arr[mid]>arr[mid+1]

因此我们根据 mid 当前所在的位置,调整二分的左右指针,就能找到顶峰。 

public int peakIndexInMountainArray(int[] arr) {
    if (arr.length== 3)
        return 1;

    int left = 1, right = arr.length - 2;
    while(left < right) {
        int mid =left+ ((right - left)>>1);
        if (arr[mid] > arr[mid - 1] && arr[mid] > arr[mid + 1])
            return mid;
        if (arr[mid] < arr[mid + 1] && arr[mid] > arr[mid - 1])
            left = mid + 1;
        if (arr[mid] > arr[mid + 1] && arr[mid] < arr[mid - 1])
            right = mid - 1;
    }
    return left;
}

1.2. 旋转数字的最小数字

我们说刷算法要按照专题来刷,这样才能看清很多题目的内在关系,二分查找也是如此,很多题目看似与二分无关,但是就是在考察二分查找,我们一起看一下。
LeetCode153 已知一个长度为 n 的数组,预先按照升序排列,经由1到n次旋转后,得到输入数组。例如原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

示例1:

输入:nums = [4,5,1,2,3]

输出:1

解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

示例2:

输入:nums = [4,5,6,7,0,1,2]

输出:0

解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。

本部分都摘自LeetCode一个不包含重复元素的升序数组在经过旋转之后,可以得到下面可视化的折线图: 

其中横轴表示数组元素的下标,纵轴表示数组元素的值。图中标出了最小值的位置,是我们需要查找的目标。
我们考虑数组中的最后一个元素 x:在最小值右侧的元素(不包括最后一个元素本身),它们的值一定都严格小于 x;而在最小值左侧的元素,它们的值一定都严格大于 x。因此,我们可以根据这一条性质,通过二分查找的方法找出最小值。
在二分查找的每一步中,左边界为 low,右边界为 high,区间的中点为 pivot,最小值就在该区间内。我们将中轴元素 nums[pivot] 与右边界元素 nums[high] 进行比较,可能会有以下的三种情况:
第一种情况是nums[pivot]<nums[high]。如下图所示,这说明nums[pivot] 是最小值右侧的元素,因此我们可以忽略二分查找区间的右半部分。

第二种情况是 nums[pivot]>nums[high]。如下图所示,这说明nums[pivot] 是最小值左侧的元素,因此我们可以忽略二分查找区间的左半部分。

由于数组不包含重复元素,并且只要当前的区间长度不为 1,pivot 就不会与high 重合;而如果当前的区间长度为 1,这说明我们已经可以结束二分查找了。因此不会存在 nums[pivot]=nums[high] 的情况。
当二分查找结束时,我们就得到了最小值所在的位置。

public int findMin(int[] nums) {
    int low = 0;
    int high = nums.length - 1;
    while (low < high) {
        int pivot = low + ((high - low) >>1);
        if (nums[pivot] < nums[high]) {
            high = pivot;
        } else {
            low = pivot + 1;
        }
    }
    return nums[low];
}

这里你是否注意到high = pivot;而不是我们习惯的high = pivot-1呢?这是为了防止遗漏元素,例如[3,1,2],执行的时候nums[pivot]=1,小于nums[high]=2,此时如果high=pivot-1,则直接变成了0。所以对于这种边界情况,很难解释清楚,最好的策略就是多写几种场景测试一下看看。这也是二分查找比较烦的情况,一般来说解释比较困难,也不容易理解清楚,所以写几个典型的例子试一下,面试的时候大部分case能过就能通过。
我们可以再拓展一下,如果在上面的基础上存在重复元素会怎么样呢?感兴趣的同学可以研究一下LeetCode154这道题。

1.3 找缺失数字

public int missingNumber (int[] a) {
  int left = 0;
  int right = a.length-1;
  while(left < right){
    int mid = (left+right)/2;
    if(a[mid]==mid){
      left = mid+1;
    }else{
      right = mid;
    }
  }
  return left;
}

1.4 优化求平方根 

剑指offer题目
实现函数 int sqrt(int x).计算并返回x的平方根这个题的思路是用最快的方式找到n*n=x的n。
如果整数没有平方根,一般采用向下取整的方式得到结果。采用折半进行比较的实现过程是:

public int sqrt (int x) {
    int l=1,r=x;
    while(l <= r){
        int mid = l + ((r - l)>>1);
        if(x/mid > mid){
            l = mid + 1;
        } else if(x / mid < mid){
            r = mid - 1;
        } else  if(x/mid == mid){
            return mid;
        }
    }
    return r;
}

 这种优化思想要记住,凡是在有序区间查找的场景,都可以用二分查找来优化速度。 如果有序区间是变化的,那就每次都针对这个变化的区间进行二分查找。这种题目在LeetCode中特别多的。

2 中序与搜索树原理

在前面我们发现很多题使用前序、后序或者层次遍历都可以解决,但几乎没有中序遍历的。这是因为中序与前后序相比有不一样的特征,例如中序可以和搜索树结合在一起,但是前后序则不行。
二叉搜索树是一个很简单的概念,但是想说清楚却不太容易。简单来说就是如果一棵二叉树是搜索树,则按照中序遍历其序列正好是一个递增序列。比较规范的定义是:

  • 若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
  • 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
  • 它的左、右子树也分别为二叉排序树。下面这两棵树一个中序序列是{3,6,9,10,14,16,19},一个是{3,6,9,10},因此都是搜索树:

搜索树的题目虽然也是用递归,但是与前后序有很大区别,主要是因为搜索树是有序的,就可以根据条件决定某些递归就不必执行了,这也称为“剪枝”。

2.1 二叉搜索树中搜索特定值

LeetCode 700.给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。例如:

target为2,给定二叉搜索树:

        4
       / \
      2   7
     / \
    1   3

 你应该返回如下子树:

      2     
     / \   
    1   3

本题看起来很复杂,但是实现非常简单,递归:

  • 如果根节点为空 root == null 或者根节点的值等于搜索值 val == root.val,返回根节点。
  • 如果 val < root.val,进入根节点的左子树查找 searchBST(root.left, val)。
  • 如果 val > root.val,进入根节点的右子树查找 searchBST(root.right, val)。 
public TreeNode searchBST(TreeNode root, int val) {
    if (root == null || val == root.val) return root;
    return val < root.val ? searchBST(root.left, val) : searchBST(root.right, val);
}

如果采用迭代方式,也不复杂:

  • 如果根节点不空 root != null 且根节点不是目的节点 val != root.val:
  • 如果 val < root.val,进入根节点的左子树查找 root = root.left。
  • 如果 val > root.val,进入根节点的右子树查找 root = root.right。 
public TreeNode searchBST(TreeNode root, int val) {
    while (root != null && val != root.val)
    root = val < root.val ? root.left : root.right;
    return root;
}

2.2 验证二叉搜索树 

LeetCode98.给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例:

 示例1:

输入:root = [2,1,3]

输出:true

示例2:

输入:root = [5,1,4,null,null,3,6]

输出:false

解释:根节点的值是 5 ,但是右子节点的值是 4 。

根据题目给出的性质,我们可以进一步知道二叉搜索树「中序遍历」得到的值构成的序列一定是升序的,在中序遍历的时候实时检查当前节点的值是否大于前一个中序遍历到的节点的值即可。

递归法:

long pre = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
    if (root == null) {
        return true;
    }
    // 如果左子树下某个元素不满足要求,则退出
    if (!isValidBST(root.left)) {
        return false;
    }
    // 访问当前节点:如果当前节点小于等于中序遍历的前一个节点,说明不满足BST,返回 false;否则继续遍历。
    if (root.val <= pre) {
        return false;
    }
    pre = root.val;
    // 访问右子树
    return isValidBST(root.right);
}

迭代法: 

   /**
     * 迭代实现
     *
     * @param root
     * @return
     */
    public static boolean isValidBST2(TreeNode root) {
        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        double inorder = 0;
        while (!stack.isEmpty() || root != null) {
            while (root != null) {
                stack.push(root);
                root = root.left;
            }
            root = stack.pop();
            // 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
            if (root.val <= inorder) {
                return false;
            }
            inorder = root.val;
            root = root.right;
        }
        return true;
    }

 如果这个题理解了,可以继续研究LeetCode530.二叉搜索树的最小绝对差和LeetCode501.二叉搜索树中的众数两个题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1276030.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IntelliJ IDEA 之初体验(上)

IntelliJ IDEA 是一款由 JetBrains 公司开发的强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;专注于 Java 开发&#xff0c;同时支持多种其他编程语言。本文将详细介绍 IntelliJ IDEA 的安装过程以及一些常用的基本操作。 第一步&#xff1a;下载与安装 IntelliJ…

LeetCode Hot100 75.颜色分类

题目&#xff1a; 给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums &#xff0c;原地对它们进行排序&#xff0c;使得相同颜色的元素相邻&#xff0c;并按照红色、白色、蓝色顺序排列。 我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 必须在不使用库内置的 so…

LeetCode 2661. 找出叠涂元素:多次映射

【LetMeFly】2661.找出叠涂元素&#xff1a;多次映射 力扣题目链接&#xff1a;https://leetcode.cn/problems/first-completely-painted-row-or-column/ 给你一个下标从 0 开始的整数数组 arr 和一个 m x n 的整数 矩阵 mat 。arr 和 mat 都包含范围 [1&#xff0c;m * n] 内…

UPDF 已集成 ChatGPT,AI 赋能注入新动能!

ChatGPT 掀起全球AI热潮,引发了世界范围内对AIGC未来会如何重塑各行各业的讨论与畅想。近日,由赛博爱思(上海)软件科技有限公司独立自主研发的国产 PDF 软件 UPDF完成版本更新,正式上线 AI 功能。据悉,UPDF AI完美集成了ChatGPT 的技术,可帮助用户智能阅读、总结、翻译、问答PD…

实现一个简单的网络通信上

那么我们的服务器有了&#xff0c;接下来就是初始化服务器 我们写的是基于udp协议的服务器&#xff0c;如果你想进行网络编程&#xff0c;那么创建的第一个就是socket 创建套接字 domain参数 所以当我们创建一个套接字时&#xff0c;你得说明未来使用这些套接字对应的类型是什…

19、串口向电脑发送数据电脑通过串口控制LED

串口向电脑发送数据 main.c #include <REGX52.H> #include "Delay.h" #include "UART.h"unsigned char Sec;void main() {//串口初始化UART_Init(); while(1){//串口发送1个字节UART_SendByte(Sec); Sec; Delay(1000); } }UART.c #include &…

学习笔记8——JUC入门基础知识

学习笔记系列开头惯例发布一些寻亲消息 链接&#xff1a;https://baobeihuijia.com/bbhj/contents/3/199561.html 进程和线程:进程是资源分配的最小单位&#xff0c;线程是CPU调度的最小单位 进程和线程的主要区别&#xff08;总结&#xff09;_进程和线程的区别-CSDN博客进程…

矩阵起源入选IDC《中国大数据管理解决方案技术评估,2023》

近日&#xff0c;矩阵起源作为典型代表厂商入选国际数据公司IDC发布的《中国大数据管理解决方案技术评估&#xff0c;2023》。 在该评估中&#xff0c;IDC认为&#xff0c;矩阵起源超融合异构数据库 MatrixOne 具备如下优势: 将存储、计算、事务三层结解耦&#xff0c;以极致灵…

uni-app 微信小程序之自定义圆形 tabbar

文章目录 1. 自定义tabbar效果2. pages新建tabbar页面3. tabbar 页面结构4. tabbar 页面完整代码 1. 自定义tabbar效果 2. pages新建tabbar页面 首先在 pages.json 文件中&#xff0c;新建一个 tabbar 页面 "pages": [ //pages数组中第一项表示应用启动页&#xff…

代理服务器的IP和端口是什么意思?

代理服务器的地址和端口&#xff1a;基础概念解析 如果我们将其与在互联网发明之前我们的老一辈之间用于交流的经典书信进行类比&#xff0c;那么地址就相当于信封上的寄件人地址&#xff0c;而端口就相当于收信人地址。然而&#xff0c;与传统信件不同&#xff0c;这里需要确切…

【蓝桥杯软件赛 零基础备赛20周】第6周——栈

文章目录 1. 基本数据结构概述1.1 数据结构和算法的关系1.2 线性数据结构概述1.3 二叉树简介 2. 栈2.1 手写栈2.2 CSTL栈2.3 Java 栈2.4 Python栈 3 习题 1. 基本数据结构概述 很多计算机教材提到&#xff1a;程序 数据结构 算法。 “以数据结构为弓&#xff0c;以算法为箭”…

使用YOLOv8训练自己的数据集

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 拉取项目 git clone https://github.com/ultralytics/ultralytics安装依赖 cd ultralytics pip install -r requirement.txt pip instal…

某60区块链安全之薅羊毛攻击实战二学习记录

区块链安全 文章目录 区块链安全薅羊毛攻击实战二实验目的实验环境实验工具实验原理实验内容薅羊毛攻击实战二 实验步骤EXP利用 薅羊毛攻击实战二 实验目的 学会使用python3的web3模块 学会分析以太坊智能合约复杂场景下薅羊毛攻击漏洞及其利用 找到合约漏洞进行分析并形成利…

springboot+vue志愿者在线报名服务管理系统java毕业设计源码+数据库

vuespringboot志愿服务管理系统 本项目是springbootvueElementuimysql源码 开发工具&#xff0c;idea和eclipse都可以,MySQL 源码下载地址 https://download.csdn.net/download/yibo2022/88401958?spm1003.2166.3001.6637.3https://download.csdn.net/download/yibo2022/884…

深入理解Zookeeper系列-1.初识Zoookeeper

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理&#x1f525;如果感觉博主的文章还不错的话&#xff…

LLM算法工程师面试题总结

一、请简述对大模型的基本原理和架构的理解。 大型语言模型如GPT&#xff08;Generative Pre-trained Transformer&#xff09;系列是基于自注意力机制的深度学习模型&#xff0c;主要用于处理和生成人类语言。下面简要概述了它们的一些基本原理和架构特点&#xff1a; 基本原…

springBoot3.2 + jdk21 + GraalVM上手体验

springBoot3.2 jdk21 GraalVM上手体验 SpringBoot2.x官方已经停止维护了&#xff0c;jdk8这次真的得换了&#x1f923; 可以参考官方文章进行体验&#xff1a;https://spring.io/blog/2023/09/09/all-together-now-spring-boot-3-2-graalvm-native-images-java-21-and-virt…

对el-button封装使用

x改变el-button样式并且多处使用 el-button封装&#xff1a; <template><el-buttonclass"my-btn":style"{ width }":disabled"disabled"click"myClick"mouseenter"person.active true"mouseleave"person.ac…

计算机视觉(OpenCV+TensorFlow)

计算机视觉&#xff08;OpenCVTensorFlow&#xff09; 文章目录 计算机视觉&#xff08;OpenCVTensorFlow&#xff09;前言3.图像金字塔3.1 高斯金字塔3.2 拉普拉斯金字塔 4.图像轮廓图像边缘和图像轮廓的区别检测图像绘制边缘 5.轮廓近似外接矩形外接圆 6. 模板匹配6.1 什么是…

Python实现FA萤火虫优化算法优化BP神经网络分类模型(BP神经网络分类算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 萤火虫算法&#xff08;Fire-fly algorithm&#xff0c;FA&#xff09;由剑桥大学Yang于2009年提出 , …