二分查找与搜索树高频问题-算法通关村

news2024/10/5 13:08:19

二分查找与搜索树高频问题-算法通关村


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

1.1 山脉数组的封顶索引

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

  • 这个题最简单的方式是对数组进行一次遍历。
    当我们遍历到下标i时,如果有arr[i-1]< arr[i] > arr[i+1] ,那么 i 就是我们需要找出的下标。
    其实还可以更简单一些,因为是从左开始找的,开始的时候必然是 arr[i-1] < a[i],所以只要找到第一个arrlil>arrli+1]的位置即可。代码就是:

  •   //时间复杂度是 O(n)
      public int peakIndexMountainArray(int[] arr){
              int n = arr.length;
              for(int i = 0; i < n; i++){
                  if(arr[i] > arr[i+1]){
                      return i;
                  }
              }
              return -1;
          }
    
  • 这个题能否使用二分来优化一下呢?当然可以。
    对于二分的某一个位置 mid,mid 可能的位置有3种情況:

    • mid在上升阶段的时候,满足 arr[mid] > arr[mid-1] && arr[mid] < arr[mid+1]
    • mid在顶峰的时候,满足 arr[i] > arr[i-1] && arr[i] > arr[i+1]
    • mid在 下降阶段,满足 arr[mid]< a[mid-1] && arr[mid]> arr[mid+1] 因此我们根据 mid 当前所在的位置,调整二分的左右指针,就能找到顶峰。
  •   //时间复杂度是 O(log n)
          public int peakIndexMountainArray(int[] arr){
              if(arr.length == 3){
                  return 1;
              }
              //数组的第一个元素(索引为0)和最后一个元素
              // (索引为 arr.length - 1)都不会是山峰
              int left = 1;
              int 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次,则可以得到 [O, 1, 2, 4, 5, 6, 7]
      注意,数组 [ nums[0],nums[1],nums[2]…nums[n-1] ] 旋装一次的結果数 [nums[n-1], nums[0], nums[1],nums[2],…,nums[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;而在最小值左侧的元素,它们的值一定都严格大于×。因此,我们可以根据这一条性质,通过二分查找的方法找出最小值。

  • 在二分查找的每一步中,左边界为low,右边界为 high,区间的中点为 pivot,最小值就在该区间内。我们将中轴元素 nums[pivot] 与右边界元素 nums[high] 进行比较,可能会有以下的三种情况:

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

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

  •   //时间复杂度是 O(log n)
          public int findMin(int[] nums){
              int low = 0;
              int high = nums.length-1;
              //使用二分查找的思想来缩小搜索范围,
              // 直到 low 和 high 相遇,这时 low 指向的元素就是数组中的最小值。
              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能过就能通过。


1.3 找缺失数字

  • 剑指offer: 一个长度为 n-1 的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围 0 ~ n-1之内。在范围 0~n-1 内的 n 个数字中有且只有一个数字不在该数组中,请找出这个数字。

  • 示例 1:
    输入: [0,1,3]
    输出: 2
    示例 2:
    输入: [0,1,2,3,4,5,6,7,9]
    输出: 8
  • 这个题很简单是不?从头到尾遍历一遍即可确定,但是这么简单肯定不是面试需要的。那这个题要考什么呢?就是二分查找。
    对于有序的也可以用二分查找,这里的关键点是在缺失的数字之前,必然有 nums[i] == i,在缺失的数字之后,必然有 nums[i] != i。
    因此,只需要二分找出第一个 nums[i] = i,此时下标i就是答案。若数组元素中没有找到此下标,那么缺失的就是n。代码如下:

  •   public int missingNumber(int[] arr){
              int left = 0;
              int right = arr.length;
              while(left < right){
                  int mid = left + ((right - left) >> 1);
                  //说明当前索引 mid 的元素是存在的,
                  // 缺失的元素在 mid 的右侧或者就是 mid
                  if(arr[mid] == mid){
                      left = mid+1;
                  }else{
                      right = mid-1;
                  }
              }
              return left;
          }
    

1.4 优化求平方根

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

  •   public int sqrt(int x){
              //分别指向搜索范围的下界(1)和上界(即 x 本身)
              int left = 1, right = x;
              while(left <= right){
                  int mid = left + ((right - left)  >> 1);
                  if(mid * mid == x){
                      return mid;
                  }else if(mid * mid > x){
                      right = mid-1;
                  }else if(mid * mid < x){
                      left = mid+1;
                  }
              }
              return right;
          }
    
  • 这种优化思想要记住,凡是在有序区间查找的场景,都可以用二分查找来优化速度。如果有序区间是变化的,那就每次都针对这个变化的区间进行二分查找。


2 中序与搜索树原理

  • 我们发现很多题使用前序、后序或者层次遍历都可以解决,但几乎没有中序遍历的。这是因为中序与前后序相比有不一样的特征,例如中序可以和搜索树结合在一起,但是前后序则不行。
  • 二又搜索树是一个很简单的概念,但是想说清楚却不太容易。简单来说就是如果一棵二叉树是搜索树,则**按照中序遍历其序列正好是一个递增序列**。比较规范的定义是:
    •若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
    •若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
    •它的左、右子树也分别为二叉排序树。下面这两棵树一个中序序列是{3,6,9,10,14,16,19},一个是{3,6,9,10},因此都是搜索树:
  • 搜索树的题目虽然也是用递归,但是与前后序有很大区别,主要是因为搜索树是有序的,就可以根据条件决定某些递归就不必执行了,这也称为“剪枝”。

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

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

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

    • 如果根节点为空 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 || root.val == val){
                  return root;
              }
              if(val > root.val){
                  return  searchBST(root.right, val);
              }
              if(val < root.val){
                  return searchBST(root.left, val);
              }
              return root;
          }
    
  • 如果采用**迭代方式**,也不复杂:

    • 如果根节点不空 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 && root.val != val)
              {
                  //说明目标值可能在右子树中
                  if(val > root.val){
                      root = root.right;
                  }
                  //说明目标值可能在左子树中
                  if(val < root.val){
                      root = root.left;
                  }
              }
              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;
              }
      
              //如果左子树无效,或者在左子树中已经发现了违反二叉搜索树性质的节点
              // (即左子树中的某个节点的值大于或等于当前节点的值),
              // 则整个树无效,返回 false。
              if(!isValidBST(root.left)){
                  return false;
              }
              //访问当前节点:如果当前节点小于等于中序遍历的前一个节点。
              //说明不满足BST,返回false
              if(root.val <= pre){
                  return false;
              }
              pre = root.val;
              //访问右子树
              return isValidBST(root.right);
          }
    
    
    
    

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

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

相关文章

JavaEE 初阶篇-深入了解线程池(线程池创建、线程池如何处理任务)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 线程池概述 1.1 线程池的优点 1.2 不使用线程池的问题 1.3 线程池的工作原理图 1.4 如何创建线程池&#xff1f; 2.0 通过 ThreadPoolExecutor 类自定义创建线程…

数据结构与算法笔记:递归函数设计技巧

ACM金牌带你零基础直达C语言精通-课程资料 本笔记属于船说系列课程之一&#xff0c;课程链接&#xff1a; 哔哩哔哩_bilibilihttps://www.bilibili.com/cheese/play/ep66799?csourceprivate_space_class_null&spm_id_from333.999.0.0 你也可以选择购买『船说系列课程-年度…

Tensorflow2.0笔记 - 自定义Layer和Model实现CIFAR10数据集的训练

本笔记记录使用自定义Layer和Model来做CIFAR10数据集的训练。 CIFAR10数据集下载&#xff1a; https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz 自定义的Layer和Model实现较为简单&#xff0c;参数量较少&#xff0c;并且没有卷积层和dropout等&#xff0c;最终准确率…

穿越代码之海:探寻结构体深层逻辑,展望未来应用新天地

欢迎来到白刘的领域 Miracle_86.-CSDN博客 系列专栏 C语言知识 先赞后看&#xff0c;已成习惯 创作不易&#xff0c;多多支持&#xff01; 结构体作为一种数据结构&#xff0c;其定义和特点决定了它在各种应用中的广泛适用性。随着科技的进步和新兴行业的不断涌现&#xf…

测试自动化流程设计思路

a) 背景介绍 基于当前互联网敏捷开发的现状&#xff0c;手工人力测试已不足以满足当前快速的版本迭代&#xff1b;以下将介绍一种可实现的自动化设计与使用。 b) 当前版本迭代流程 研发同学从代码库master分支拉出新代码进行研发工作得开发开发完成之后提交到代码库测试同学介入…

从概念到实践:探索独立站在当代电商中的关键作用

随着数字化时代的到来&#xff0c;电子商务已成为全球商业生态的核心组成部分。在这个不断变化的市场中&#xff0c;独立站作为企业建立在线身份和拓展业务的强大工具&#xff0c;正逐步展现出其不可替代的价值。 从概念到实践&#xff0c;本文将深入探索独立站在当代电商中的关…

C++从入门到精通——类的作用域及类的实例化

类的作用域及类的实例化 前言一、类的作用域二、类的实例化引例类是对对象进行描述的示例 一个类可以实例化出多个对象示例 示例 前言 类的作用域是指类中定义的变量和方法的可见性和可访问性范围。在类的内部&#xff0c;所有成员&#xff08;包括属性和方法&#xff09;都具…

LeetCode-51. N 皇后【数组 回溯】

LeetCode-51. N 皇后【数组 回溯】 题目描述&#xff1a;解题思路一&#xff1a;回溯&#xff0c; 回溯三部曲。验证是否合法只需要检查:1.正上方&#xff1b;2. 左上方&#xff1b;3.右上方。因为是从上到下&#xff0c;从左到右遍历的&#xff0c;下方不可能有皇后。解题思路…

Day60:WEB攻防-XMLXXE安全无回显方案OOB盲注DTD外部实体黑白盒挖掘

目录 XML&XXE-传输-原理&探针&利用&玩法 XXE 黑盒发现 XXE 白盒发现 XXE修复防御方案 有回显 无回显 XML&XXE-黑盒-JSON&黑盒测试&类型修改 XML&XXE-白盒-CMS&PHPSHE&无回显 知识点&#xff1a; 1、XXE&XML-原理-用途&…

Unity与CocosCreator对比学习二

一、锚点与适配 1.在Creator中 适配通过锚点、位置和Widget达到适配目的&#xff1b;锚点是节点在其父节点坐标系中坐标对其点&#xff0c;其x,y范围在[0, 1]之间&#xff1b; 锚点为(0, 0)时在节点自身的左下角&#xff0c;节点坐标指其左下角在父节点中的坐标&#xff1b;锚…

【2024系统架构设计】案例分析- 5 Web应用

目录 一 基础知识 二 真题 一 基础知识 1 Web应用技术分类 大型网站系统架构的演化:高性能、高可用、可维护、应变、安全。 从架构来看:MVC,MVP,MVVM,REST,Webservice,微服务。

内存管理(SRAM)

内存管理介绍 内存管理实际上就是指管理SRAM. 内存管理&#xff0c;是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如 何高效、快速的分配&#xff0c;并且在适当的时候释放和回收内存资源。内存管理的实现方法有很多种&#xff0c;其实最终都是要实现两…

用vscode仿制小米官网

html内容: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><link rel&quo…

无线基本认识和配置

1、无线局域网 IEEE 802.11标准 根据应用范围分类 WPAN --- 个人无线网络 NFC、ZIgbee、Bluetooth WLAN --- 无线局域网 WiFi&#xff0c;使用到WPAN技术 WMAN --- 无线城域网 WiMax 802.16 WWAN --- 无线广域网 GSM、CDMA、WCDMA、LTE、5G、TD-SCDMA 2、…

PG 中的 MAXALIGN 及对齐分配内存(MemoryContextAllocAligned)

在PG源码中&#xff0c;MAXALIGN这个宏&#xff0c;返回最接近输入数字&#xff08;大于&#xff09;且能整除8的数&#xff0c;仅此而已。 常用于内存相关的计算&#xff0c;在PG代码中使用的相当广泛&#xff0c;为啥要用这个MAXALIGN&#xff1f;我估计是基于 “CPU访问对齐…

数据库同步方案Sqlserver

数据库同步方案探究 随着信息技术的迅猛发展&#xff0c;数据库在各个领域的应用日益广泛。而在分布式系统、云计算、大数据等场景下&#xff0c;数据库同步成为了一个至关重要的问题。数据库同步不仅关乎数据的完整性和一致性&#xff0c;还直接影响到系统的稳定性和性能。因…

加薪非要老总批?--责任链模式

1.1 老板&#xff0c;我要加薪 "我和刚进来的几个同事比较&#xff0c;我觉得我做得很好。公司每每分配的任务&#xff0c;我基本都可以快速完成。有一次&#xff0c;一段程序需要增加一个分支条件&#xff0c;我立刻想到利用反射、工厂等设计模式来处理&#xff0c;经理对…

移除元素 -- 力扣第27题 -- 暴力、双指针解法

题目 https://leetcode.cn/problems/remove-element/description/ 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并原地修改输…

Maven--lib分离的打包方式

就是把lib包和source源码分开打包。优势就是&#xff0c;面对频繁更新的应用场景时&#xff0c;可以只更新源码包&#xff08;当然&#xff0c;前提是你的依赖没有增减&#xff09;。尤其是使用jenkins更新项目时&#xff0c;会省去很多时间吧&#xff1f; 不同项目的 lib之间不…

yolov9直接调用zed相机实现三维测距(python)

yolov9直接调用zed相机实现三维测距&#xff08;python&#xff09; 1. 相关配置2. 相关代码2.1 相机设置2.2 测距模块2.2 实验结果 相关链接 此项目直接调用zed相机实现三维测距&#xff0c;无需标定&#xff0c;相关内容如下&#xff1a; 1. yolov4直接调用zed相机实现三维测…