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

news2024/9/22 21:20:27

文章目录

  • 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++)
        //找到target
        if(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) {
        // basecase
        if(nums.length == 0){
            return 0;
        }
        //左右指针
        int l = 0 , r = nums.length - 1;

        //循环
        while(l < r){
            //找到数组中的重点m
            int 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 here
        int 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.coloradmin.cn/o/908883.html

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

相关文章

wustojc日期格式变化

#include <stdio.h> int main() {char a[10];for(int i0;i<10;i){//用一个耍聪明的方法&#xff0c;全部用数组存储&#xff1b;面向结果编程a[0]getchar();}printf("%c%c%c%c%c%c%c%c%c%c",a[6],a[7],a[8],a[9],a[2],a[0],a[1],a[5],a[3],a[4]);return 0;}…

什么是跳跃表 ? 说一说跳跃表的查询和新增流程 ?

1.什么是跳跃表&#xff08;Skip List&#xff09; 跳跃表是 ZSet 有序列表底层的一种实现&#xff0c;也成为跳表。它通过添加多层链表的方式&#xff0c;用于在有序集合中进行高效的查找操作。 简单跳跃表的结构图&#xff1a; 从图中可以看出跳跃表有这些特征&#xff1a; …

Nginx-URLRewrite伪静态

URLRwrite是指将真实地址隐藏&#xff0c;用户访问是通过伪地址进行访问&#xff0c;这样可以隐藏URL中的传参等等 URLwrite演示&#xff0c;浏览器输入伪URL&#xff0c;回车会跳转到真实URL Rewrite匹配规则 redirect是指当请求伪装地址后&#xff0c;页面会直接跳转到真实…

基于微信小程序的上门维修评价系统_22c7h-

随着科学研究的不断深入&#xff0c;有关上门维修的各种信息量也在成倍增长。面对庞大的信息量&#xff0c;就需要有上门维修系统来提高管理工作的效率。通过这样的系统&#xff0c;我们可以做到信息的规范管理和快速查询&#xff0c;从而减少了管理方面的工作量。 建立基于微信…

聊聊 Docker

聊聊 Docker Docker 是什么&#xff1f; 定义 Docker 是一款 开源的应用容器引擎。 简单来说&#xff0c;就是 以容器虚拟化技术为基础的软件。可以把应用程序和所依赖的包一起打包到一个可移植的镜像中&#xff0c;发布到 Linux 或者 Windows 上运行。&#xff08;代码 运…

数据通信——传输层(传输层概述)

引言 终于到传输层了&#xff0c;网络层还有很多需要补充的&#xff0c;后期在慢慢填补了。 我们看哈&#xff01;在物理层我们设计出来各种硬件&#xff0c;然后使它们在物理上相互连接&#xff0c;信号以比特流的形式进行发送&#xff1b;随后&#xff0c;在数据链路层&#…

Mybatis介绍和搭建(详细搭建步骤)

目录 一、mybatis介绍 官方简介 通俗易懂 二、搭建步骤 1.创建Maven项目 2.创建数据库并建表和相关类 3.创建全局配置文件,配置数据库连接信息 4.配置sql映射文件 5.测试 一、mybatis介绍 官方简介 MyBatis 是一款优秀的持久层框架&#xff0c;它支持自定义 SQL、存…

C语言和JavaScript中的默认排序行为对比

前言 今天在js里使用sort时遇见了一个不理解的现象 即使用sort默认排序后 9 从排序前的第一位被排到了最后一位.一开始我对js sort的理解和c一样&#xff0c;然后通过查阅后发现并不是这样. 正文 排序是一项常见而重要的操作。不同的编程语言提供了不同的排序函数&#xf…

Vue开发中如何解决国际化语言切换问题

Vue开发中如何解决国际化语言切换问题 引言&#xff1a; 在如今的全球化时代&#xff0c;应用程序的国际化变得越来越重要。为了让不同地区的用户能够更好地使用应用程序&#xff0c;我们需要对内容进行本地化&#xff0c;以适应不同语言和文化环境。对于使用Vue进行开发的应用…

ROS2 中的分布式系统

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

Java 计算生肖,java Data中获取年,根据生日日期获取生肖注解,根据输入时间获取生肖,自定义注解的方式获取生肖 根据年份时间获取十二生肖

最近&#xff0c;开发中需要增加生肖&#xff0c;但是不想增加字段&#xff0c;于是通过注解的方式&#xff0c;实现生日与生肖的转换。 话不多说&#xff0c;直接上代码&#xff0c;如下&#xff1a; 实体类中的字段&#xff0c;添加自定义注解&#xff08;ToChineseZodiacSe…

各地区-不同行业-就业、失业、工资144个指标(1990-2021年)

一、数据介绍 数据名称&#xff1a;各地区-不同行业-就业、失业、工资144个指标 数据年份&#xff1a;1990-2021年&#xff08;1990-2007缺失较多&#xff09; 数据样本&#xff1a;994条 数据整理&#xff1a;自主整理 二、参考文献 [1]戚聿东,刘翠花,丁述磊.数字经济发展…

数据库系统课设——基于python+pyqt5+mysql的酒店管理系统(可直接运行)--GUI编程

几个月之前写的一个项目&#xff0c;通过这个项目&#xff0c;你能学到关于数据库的触发器知识&#xff0c;python的基本语法&#xff0c;python一些第三方库的使用&#xff0c;包括python如何将前后端连接起来&#xff08;界面和数据&#xff09;&#xff0c;还有界面的设计等…

Python OCR 使用easyocr库将图片中的文章提取出来

Python OCR 使用easyocr库将图片中的文章提取出来 初环境内容步骤一&#xff1a;安装easyocr库步骤二&#xff1a;导入必要的库步骤三&#xff1a;创建OCR阅读器对象步骤四&#xff1a;指定要识别的图片路径步骤五&#xff1a;执行OCR识别并提取文章内容步骤六&#xff1a;遍历…

vscode如何关闭受限模式

受限模式旨在实现安全地浏览代码 第一步&#xff1a; 第二步&#xff1a;输入trust,找到Security.workspace.trust,取消勾选 第三步&#xff1a; 重启vscode.

企业都在用的热门客服系统

客户服务是现如今企业抢占市场的重要因素。客户越来越重视企业服务&#xff0c;而优质的客户服务也会为企业带来正向且长期的收益。而市场上这么多客户服务系统哪个好用&#xff1f; 企业选购好用的客户服务系统的时候要注意以下几点&#xff1a; 一、选购注意事项&#xff1…

TheGem主题 - 创意多用途和高性能WooCommerce WordPress主题/网站

TheGem主题概述 – 适合所有人的TheGem 作为设计元素、样式和功能的终极 Web 构建工具箱而设计和开发&#xff0c;TheGem主题将帮助您在几分钟内构建一个令人印象深刻的高性能网站&#xff0c;而无需触及一行代码。不要在编码上浪费时间&#xff0c;探索你的创造力&#xff01…

Python“牵手”1688商品详情数据采集方法,1688API申请步骤说明

1688平台API接口是为开发电商类应用程序而设计的一套完整的、跨浏览器、跨平台的接口规范。 1688API接口是指通过编程的方式&#xff0c;让开发者能够通过HTTP协议直接访问1688平台的数据&#xff0c;包括商品信息、店铺信息、物流信息&#xff0c;评论数据&#xff0c;店铺订…

rtmp直播

技术要求&#xff1a;nginxnginx-rtmpffmpegVLC 跟着大佬走的&#xff1a; 传送门 准备工作&#xff1a; 首先需要一台公网ip的服务器 这是使用天翼云的弹性云主机&#xff1a;免费试用1个月 天翼云官网 点击关机&#xff0c;更多里面选择重置密码&#xff0c; 默认用户名为…

EMQ 成功通过 SOC 2 Type I 认证,为全球客户的数据安全保驾护航

近日,EMQ 正式地通过了国际权威鉴证标准 SOC 2 审计,并荣获国际四大会计师事务所之一颁发的 SOC 2 Type I 鉴证报告,这使其成为全球物联网 MQTT 消息服务的安全践行者。此次认证进一步证实,EMQ 为其旗下的全托管 MQTT 消息云服务 EMQX Cloud 以及企业级 MQTT 物联网接入平台 EM…