【二分查找篇】速刷牛客TOP101 高效刷题指南

文章目录

  • 17、BM17 二分查找-I
  • 18、BM18 二维数组中的查找
  • 19、BM19 寻找峰值
  • 20、BM20 数组中的逆序对
  • 21、BM21 旋转数组的最小数字
  • 22、BM22 比较版本号
  • 23、BM23 二叉树的前序遍历

17、BM17 二分查找-I

在这里插入图片描述

思路步骤:

  • step 1:从数组首尾开始,每次取中点值。
  • step 2:如果中间值等于目标即找到了,可返回下标,如果中点值大于目标,说明中点以后的都大于目标,因此目标在中点左半区间,如果中点值小于目标,则相反。
  • step 3:根据比较进入对应的区间,直到区间左右端相遇,意味着没有找到。
public class Solution {/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param nums int整型一维数组 * @param target int整型 * @return int整型*/public int search (int[] nums, int target) {// 二分查找int l = 0;int r = nums.length - 1;//从数组首尾开始,直到二者相遇while(l <= r){//每次检查中点的值int m = (l + r) / 2;if(nums[m] == target){return m;}//进入左的区间if(nums[m] > target){r = m - 1;}else{l = m + 1;}}//未找到return -1;}
}

18、BM18 二维数组中的查找

在这里插入图片描述

思路步骤:

似乎我们可以直接从上到下遍历矩阵,再从左到右遍历矩阵每一行,然后检验目标值是否是遇到的元素。

//两层循环,遍历二维数组
for(int i = 0; i < n; i++)  for(int j = 0; j < m; j++)//找到targetif(array[i][j] == target)  return true;

但是我们这样就没有利用到矩阵内部的行列都是有序这个性质,我们再来找找规律:

首先看四个角,左上与右下必定为最小值与最大值,而左下与右上就有规律了:左下元素大于它上方的元素,小于它右方的元素,右上元素与之相反。既然左下角元素有这么一种规律,相当于将要查找的部分分成了一个大区间和小区间,每次与左下角元素比较,我们就知道目标值应该在哪部分中,于是可以利用分治思维来做。

具体做法:

  • step 1:首先获取矩阵的两个边长,判断特殊情况。
  • step 2:首先以左下角为起点,若是它小于目标元素,则往右移动去找大的,若是他大于目标元素,则往上移动去找小的。
  • step 3:若是移动到了矩阵边界也没找到,说明矩阵中不存在目标值。

图示:

图片说明

public class Solution {public boolean Find (int target, int[][] array) {// 二分查找//basecase//首先判断矩阵的两个边长if(array.length == 0){return false;}if(array[0].length == 0){return false;}//定义两个变量int m = array.length;int n = array[0].length;//从最左下角开始往右或者往上找for(int i = 0, j = n -1; i < m && j >= 0;){//小于目标值,开始往右找if(array[i][j] < target){i++;}else if(array[i][j] > target){j--;}else{return true;}}return false;  }
}

19、BM19 寻找峰值

在这里插入图片描述

思路:

因为题目将数组边界看成最小值,有两种情况:

  1. 往下坡方向走,有可能遇到新的山峰,也有可能一路走到深渊谷底;
  2. 往上坡方向走,由于最后面是深渊,所以一定能找到山峰;

而我们只需要找到其中一个波峰,

因此只要不断地往高处走,一定会有波峰。

那我们可以每次找一个标杆元素,将数组分成两个区间,每次就较高的一边走

因此也可以用分治来解决,而标杆元素可以选择区间中点。

具体做法:

  • step 1:二分查找首先从数组首尾开始,每次取中间值,直到首尾相遇。
  • step 2:如果中间值的元素大于它右边的元素,说明往右是向下,我们不一定会遇到波峰,但是那就往左收缩区间。
  • step 3:如果中间值大于右边的元素,说明此时往右是向上,向上一定能有波峰,那我们往右收缩区间。
  • step 4:最后区间收尾相遇的点一定就是波峰。

图示:

alt

public class Solution {public int findPeakElement (int[] nums) {//关键思想:下坡的时候可能找到波峰,但是可能找不到,一直向下走的//上坡的时候一定能找到波峰,因为题目给出的是nums[-1] = nums[n] = -∞int l = 0;int r = nums.length - 1;while(l < r){//int mid = l + ((l - r)>>1);int mid = (l + r) / 2;//右边是往下,不一定有波峰,就往左边找if(nums[mid] > nums[mid + 1]){r = mid;}else{//右边是往上,一定有波峰l = mid + 1;}   }return r; }
}

20、BM20 数组中的逆序对

在这里插入图片描述

思路步骤

那么,我们先来说说归并算法吧,归并算法讲究一个先分后并!

先分:分呢,就是将数组分为两个子数组,两个子数组分为四个子数组,依次向下分,直到数组不能再分为止!

后并:并呢,就是从最小的数组按照顺序合并,从小到大或从大到小,依次向上合并,最后得到合并完的顺序数组!

介绍完归并排序,我们来说说归并统计法,我们要在哪个步骤去进行统计呢?

归并统计法,关键点在于合并环节,在合并数组的时候,当发现右边的小于左边的时候,此时可以直接求出当前产生的逆序对的个数。

举个例子:

在合并 {4 ,5} {1 , 2} 的时候,首先我们判断 1 < 4,我们即可统计出逆序对为2,为什么呢?这利用了数组的部分有序性。因为我们知道 {4 ,5} 这个数组必然是有序的,因为是合并上来的。此时当 1比4小的时候,证明4以后的数也都比1大,此时就构成了从4开始到 {4,5}这个数组结束,这么多个逆序对(2个),此时利用一个临时数组,将1存放起来,接着比较2和4的大小,同样可以得到有2个逆序对,于是将2也放进临时数组中,此时右边数组已经完全没有元素了,则将左边剩余的元素全部放进临时元素中,最后将临时数组中的元素放进原数组对应的位置。

最后接着向上合并~

可以看到下面这张图~

image-20210623223031128

public class Solution {int count = 0;public int InversePairs (int[] nums) {// 归并排序//长度小于2则无逆序对if (nums == null || nums.length < 2) {return 0;}//将数组分为左右两部分进行排序mergeSort(nums, 0, nums.length - 1);return count;}private void mergeSort(int[] arr, int L, int R) {//basecase//如果L下标和R下标重合,那也说明有序if (L == R) {return;}//找分割点int mid = L + ((R - L) >> 1);//将 左边部分排好序mergeSort(arr, L, mid);//将 右边部分排好序mergeSort(arr, mid + 1, R);//merge,将两部分排好序的数组进行整体排序merge(arr, L, mid, R);}public void merge(int[] arr, int L, int mid, int R) {//首先定义一个help数组,长度为此时两个子数组加起来的长度int[] help = new int[R - L + 1];//临时数组的下标起点int i = 0;//定义两个指针int p1 = L;int p2 = mid + 1;//如果这两个数组都没有越界while (p1 <= mid && p2 <= R) {// 当左子数组的当前元素小的时候,跳过,无逆序对if (arr[p1] <= arr[p2]) {// 放入临时数组help[i++] = arr[p1++];} else { // 否则,此时存在逆序对// 放入临时数组help[i++] = arr[p2++];// 逆序对的个数为    左子数组的终点- 当前左子数组的当前指针count += mid - p1 + 1;count %= 1000000007;}//help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];}//如果左边的数组没有越界while (p1 <= mid) {//将左边数组的值直接拷贝到hele数组里面help[i++] = arr[p1++];}//如果右边的数组没有越界while (p2 <= R) {//将右边数组的值直接拷贝到hele数组里面help[i++] = arr[p2++];}//经过上面的步骤,help数组有序啦,拷贝到arr数组就完成啦for (i = 0; i < help.length; i++) {arr[L + i] = help[i];}}
}

21、BM21 旋转数组的最小数字

在这里插入图片描述

解题思路:

排序数组的查找问题首先考虑使用 二分法 解决,其可将 遍历法 的 线性级别 时间复杂度降低至 对数级别

算法流程:

  1. 初始化: 声明 i, j 双指针分别指向 array 数组左右两端

  2. 循环二分: 设 m = (i + j) / 2 为每次二分的中点( “/” 代表向下取整除法,因此恒有 i≤m1、当 array[m] > array[j] 时: m 一定在 左排序数组 中,即旋转点 x 一定在 [m + 1, j] 闭区间内,因此执行 i = m + 1

  3. 当 array[m] < array[j] 时: m 一定在 右排序数组 中,即旋转点 x 一定在[i, m]闭区间内,因此执行 j = m

  4. 当 array[m] = array[j] 时: 无法判断 mm 在哪个排序数组中,即无法判断旋转点 x 在 [i, m] 还是 [m + 1, j] 区间中。

    解决方案: 执行 j = j - 1 缩小判断范围

  5. 返回值: 当 i = j 时跳出二分循环,并返回 旋转点的值 array[i] 即可。

public class Solution {/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param nums int整型一维数组 * @return int整型*/public int minNumberInRotateArray (int[] nums) {// basecaseif(nums.length == 0){return 0;}//左右指针int l = 0 , r = nums.length - 1;//循环while(l < r){//找到数组中的重点mint m = l + (r - l)/2;// m在左排序数组中,旋转点在 [m+1, r] 中if(nums[m] > nums[r]){l = m + 1;// m 在右排序数组中,旋转点在 [l, m]中    }else if(nums[m] < nums[r]){r = m;}else{// 缩小范围继续判断r--;}}// 返回旋转点return nums[l];}
}

22、BM22 比较版本号

在这里插入图片描述

思路:

既然是比较两个字符串每个点之间的数字是否相同,就直接同时遍历字符串比较,因此我们需要使用两个同向访问的指针各自访问一个字符串。

比较的时候,数字前导零不便于我们比较,因为我们不知道后面会出现多少前导零,因此应该将点之间的部分转化为数字再比较才方便。

while(i < n1 && version1[i] != '.'){ num1 = num1 * 10 + (version1[i] - '0');i++;
}

具体做法:

  • step 1:利用两个指针表示字符串的下标,分别遍历两个字符串。
  • step 2:每次截取点之前的数字字符组成数字,即在遇到一个点之前,直接取数字,加在前面数字乘10的后面。(因为int会溢出,这里采用long记录数字)
  • step 3:然后比较两个数字大小,根据大小关系返回1或者-1,如果全部比较完都无法比较出大小关系,则返回0
public class Solution {/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** 比较版本号* @param version1 string字符串 * @param version2 string字符串 * @return int整型*/public int compare (String version1, String version2) {// write code hereint n1 = version1.length();int n2 = version2.length();int i = 0, j = 0;//遍历直到某个字符串结束while(i < n1 || j < n2){long num1 = 0;//从下一个点前截取数字while(i < n1 && version1.charAt(i) != '.'){num1 = num1 * 10 + (version1.charAt(i) - '0');i++;}//跳过点i++;long num2 = 0;//从下一个点前截取数字while(j < n2 && version2.charAt(j) != '.'){num2 = num2 * 10 + (version2.charAt(j) - '0');j++;}//跳过点j++;//比较数字大小if(num1 > num2){return 1;}if(num1 < num2){return -1;}}//版本号相同return 0;}
}

23、BM23 二叉树的前序遍历

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

“SRP模型+”多技术融合在生态环境脆弱性评价模型构建、时空格局演变分析与RSEI 指数的生态质量评价

近年来&#xff0c;国内外学者在生态系统的敏感性、适应能力和潜在影响等方面开展了大量的生态脆弱性研究&#xff0c;他们普遍将生态脆弱性概念与农牧交错带、喀斯特地区、黄土高原区、流域、城市等相结合&#xff0c;评价不同类型研究区的生态脆弱特征&#xff0c;其研究内容…

【Git】本地搭建Gitee、Github环境

本地 &#xff08;Local&#xff09; 1、使用命令生成公钥&#xff08;pub文件&#xff09; 1. $ ssh-keygen -t rsa -C "xxxxxxxemail.com" -f "github_id_rsa" 2. $ ssh-keygen -t rsa -C "xxxxxxxemail.com" -f "gitee_id_rsa" …

ROS2 中的分布式系统

一、说明 当您运行 ROS2 应用程序时&#xff0c;通常需要在不同机器的不同位置运行 ROS2 节点。由于 ROS2 在抽象的 DDS 层中使用节点之间的通信&#xff0c;因此我们可以非常轻松地安排通信。 为了充分理解 ROS2 的架构&#xff0c;我建议您熟悉本文。 出于本文的目的&#xf…

GEEMAP 中如何拉伸图像

图像拉伸是最基础的图像增强显示处理方法&#xff0c;主要用来改善图像显示的对比度&#xff0c;地物提取流程中往往首先要对图像进行拉伸处理。图像拉伸主要有三种方式&#xff1a;线性拉伸、直方图均衡化拉伸和直方图归一化拉伸。 GEE 中使用 .sldStyle() 的方法来进行图像的…

Vscode详细安装教程

Vscode官网下载 官网地址&#xff1a;Download Visual Studio Code - Mac, Linux, Windows 通过链接可以直接跳转到下面的页面当中&#xff0c;支持的版本有Windows、Linux、Mac&#xff0c;可以选择适配自己电脑的版本&#xff0c;一般来说应该是Windows x64的。不要直接点W…

无涯教程-PHP - 循环语句

PHP中的循环用于执行相同的代码块指定的次数。 PHP支持以下四种循环类型。 for - 在代码块中循环指定的次数。 while - 如果且只要指定条件为真&#xff0c;就会循环遍历代码块。 do ... while - 循环执行一次代码块&#xf…

数据同步工具比较:选择适合您业务需求的解决方案

在当今数字化时代&#xff0c;数据已经成为企业的核心资产。然而&#xff0c;随着业务的扩展和设备的增多&#xff0c;如何实现数据的高效管理和同步成为了一个亟待解决的问题。本文将介绍几种常见的数据同步工具&#xff0c;并对比它们的功能、性能和适用场景&#xff0c;帮助…

【boost网络库从青铜到王者】第三篇:asio网络编程中的buffer缓存数据结构

文章目录 1、关于buffer数据结构1.1、简单概括一下&#xff0c;我们可以用buffer() 函数生成我们要用的缓存存储数据。1.2、但是这太复杂了&#xff0c;可以直接用buffer函数转化为send需要的参数类型:1.3、output_buf可以直接传递给该send接口。我们也可以将数组转化为send接受…

【计算机网络八股】计算机网络(一)

目录 计算机网络的各层协议及作用&#xff1f;TCP和UDP的区别&#xff1f;UDP 和 TCP 对应的应用场景是什么&#xff1f;详细介绍一下 TCP 的三次握手机制&#xff1f;为什么需要三次握手&#xff0c;而不是两次&#xff1f;为什么要三次握手&#xff0c;而不是四次&#xff1f…

【李群李代数】李群控制器(lie-group-controllers)介绍——控制 SO(3) 空间中的系统的比例控制器Demo...

李群控制器SO(3)测试 测试代码是一个用于控制 SO(3) 空间中的系统的比例控制器。它通过计算控制策略来使当前状态逼近期望状态。该控制器使用比例增益 kp 进行参数化&#xff0c;然后进行一系列迭代以更新系统状态&#xff0c;最终检查状态误差是否小于给定的阈值。这个控制器用…

rabbitmq的死信队列

目录 成为死信的条件 消息TTL过期 队列达到最大长度 消息被拒 延迟队列 延迟队列使用场景 消息设置 TTL 队列设置 TTL 两者区别 producer 将消息投递到 broker 或者直接到 queue 里了&#xff0c; consumer 从 queue 取出消息 进行消费&#xff0c;但某些时候由…

第十五课、Windows 下打包发布 Qt 应用程序

功能描述&#xff1a;讲解了 Windows 下打包发布 Qt 应用程序的三种方法&#xff0c;并对比优缺点 一、利用 windepolyqt 工具打包发布 Qt 提供了一个 windeployqt 工具来自动创建可部署的文件夹。 打包发布流程&#xff1a; 1. 新建一个文件夹&#xff0c;将编译后的可执行…