算法篇之二分

二分算法简介

特点

最简单的一种算法,也是最恶心,细节最多,最容易写出死循环的算法
时间复杂度O(logN)

如何学习

  1. 明白其中的算法原理,二分并不是只有数组有序的的时候使用,而是看是否具有二段性。
  2. 模板
    1. 朴素的二分模板(easy,有局限性)
    2. 查找左边界的二分模板
    3. 查找右边界的二分模板

b,c两种模板是万能模板,但是细节多

二分查找

image.png

题目链接:二分查找

算法思路:

image.png

代码
class Solution {
    public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        // 当left == right的时候还未判断
        while(left <= right) {
            int mid = left + (right - left) / 2;
            if(nums[mid] < target) {
                left = mid + 1;
            }else if(nums[mid] > target) {
                right = mid - 1;
            }else {
                return mid;
            }
        }
        return -1;
    }
}

朴素二分模板

image.png

在排列数组中查找元素的第一个和最后一个位置

image.png

题目链接:在排列数组中查找元素的第一个和最后一个位置

算法思路

image.png

代码
class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] result = new int[2];
        result[0] = result[1] = -1;
        if(nums.length == 0) return result;
        // 寻找区间左端点()
        int left = 0, right = nums.length - 1;
        // 1. left<=right会死循环,当left=right找到target时,进入循环还会走right=mid,一直循环下去。
        // 2. 当left==right时就是答案,不需要再判断了
        while(left < right) {
            // 如果数组是偶数要取左边元素
            // 因为取右边的元素会死循环,比如如果剩下(1,2)两个元素
            // left=1,right=2,mid=2.更新区间后,left,right,mid的值没有改变
            int mid = left + (right - left) / 2;
            // 二段性,左区间小于target,右区间大于等于target
            if(nums[mid] < target) {
                left = mid + 1; // 由于左区间都是小于target,所以left=mid+1
            }else {
                right = mid;// 由于target在右区间间,所有right不能等于mid-1
            }
        }
        if(nums[left] != target) return result;
        result[0] = left;
        // 寻找区间右端点
        left = 0;
        right = nums.length - 1;
        while(left < right) {
            // 如果数组是偶数要取右边元素
            // 因为取左边的元素会死循环,比如如果剩下(1,2)两个元素
            // left=1,right=2,mid=2.更新区间后,left,right,mid的值没有改变
            int mid = left + (right - left + 1) / 2;
            if(nums[mid] <= target) {
                left = mid; // 由于左区间都是小于等于target,所以left不能等于mid+1
            }else {
                right = mid - 1; // 由于右区间都是小于target,所以right=mid-1
            }
        }
        result[1] = left;
        return result;
    }
}

模板

image.png

搜索插入位置

image.png

题目链接:搜索插入位置

算法思路

数组是有序数组,具有二段性。可以将数组划分左右两个区间,左区间<=target,右区间>target。即可以套用求右端点模板。

代码
class Solution {
    public int searchInsert(int[] nums, int target) {
        // 利用二段性,可以划分为左区间<=target,右区间>target
        // 也可以划分为左区间<target,右区间>=target
        // 咱们这里采用查找区间右端点模板
        int left = 0, right = nums.length - 1;
        while(left < right) {
            int mid = left + (right - left + 1) / 2;
            if(nums[mid] <= target) {
                left = mid;
            }else {
                right = mid - 1;
            }
        }
        if(nums[left] < target) return left + 1;
        return left;
     }
}

X的平方根

image.png

题目链接:X的平方根

算法思路

image.png

代码:
class Solution {
    public int mySqrt(int x) {
        // x的平分根一定在0~x之间
        long left = 0, right = x;
        // 变成求mid*mid=x
        // 因为保留整数部分,可以划分为左区间<=x,右区间>x
        while(left < right) {
            // 使用long,反溢出
            long mid = left + (right - left + 1) / 2;
            if(mid * mid <= x) {
                left = mid;
            }else {
                right = mid - 1;
            }
        }
        return (int)left;
    }
}

山脉数组的峰顶索引

image.png

题目链接:山脉数组的峰顶索引

算法思路:

image.png

代码:
class Solution {
    public int peakIndexInMountainArray(int[] arr) {
        // 利用二段性,可以将区间分为小于等于峰值的左区间,小于峰值的右区间
        // 即套用求区间右端点模板
        int left = 0, right = arr.length - 1;
        while(left < right) {
            int mid = left + (right - left + 1) / 2;
            if(arr[mid] > arr[mid - 1]) {
                left = mid;
            }else {
                right = mid - 1;
            }
        }
        return left;
    }
}

寻找峰值

image.png

题目链接:寻找峰值

算法思路:

image.png
二段性:任取一个点i,与下一个点i+1会有如下两种情况

  • nums[i] > nums[i+1]:此时「左侧区域」⼀定会存在⼭峰(因为最左侧是负⽆ 穷),那么我们可以去左侧去寻找结果;
  • nums[i] < nums[i+1]:此时「右侧区域」⼀定会存在⼭峰(因为最右侧是负⽆ 穷),那么我们可以去右侧去寻找结果;
代码:
class Solution {
    public int findPeakElement(int[] nums) {
        int left = 0, right = nums.length - 1;
        while(left < right) {
            int mid = left + (right - left) / 2;
            if(nums[mid] > nums[mid + 1]) {
                right = mid;
            }else {
                left = mid + 1;
            }
        }
        return left;
    }
}

寻找旋转排序数组中的最小值

image.png

题目链接:寻找旋转排序数组中的最小值

算法思路:

image.png
⼆分的本质:找到⼀个判断标准,使得查找区间能够⼀分为⼆。
通过图像我们可以发现, [A,B] 区间内的点都是严格⼤于 D 点的值的, C 点的值是严格小于 D 点的值的。但是当 [C,D] 区间只有⼀个元素的时候, C 点的值是可能等于 D 点的值的。
因此,初始化左右两个指针 left , right :
然后根据 mid 的落点,我们可以这样划分下⼀次查询的区间:

  • 当 mid 在 [A,B] 区间的时候,也就是 mid 位置的值严格⼤于 D 点的值,下⼀次查询区间在 [mid + 1,right] 上;
  • 当 mid 在 [C,D] 区间的时候,也就是 mid 位置的值严格⼩于等于 D 点的值,下次查询区间在 [left,mid] 上。

当区间⻓度变成 1 的时候,就是我们要找的结果。

代码:
class Solution {
    public int findMin(int[] nums) {
        int left = 0, right = nums.length - 1;
        int n = nums.length;
        while(left < right) {
            int mid = left + (right - left) / 2;
            if(nums[mid] > nums[n - 1]) {
                left = mid + 1;
            }else {
                right = mid;
            }
        }
        return nums[left];
    }
}

0到n-1中缺失的数字

image.png

题目链接:0到n-1中缺失的数字

算法思路:

image.png
在这个升序的数组中,我们发现:

  • 在第⼀个缺失位置的左边,数组内的元素都是与数组的下标相等的;
  • 在第⼀个缺失位置的右边,数组内的元素与数组下标是不相等的。

因此,我们可以利⽤这个「⼆段性」,来使⽤「⼆分查找」算法。

代码:
class Solution {
    public int takeAttendance(int[] records) {
        // 可以划分为左区间数值和下标相等,右区间数值和下标不相等
        int left = 0, right = records.length - 1;
        while(left < right) {
            int mid = left + (right - left) / 2;
            if(records[mid] == mid) {
                left = mid + 1;
            }else {
                right = mid;
            }
        }
        // 注意处理数组只有一个数的情况
        return records[left] == left ? left + 1 : left;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JDK版本如何在IDEA中切换

JDK版本在IDEA中切换 一、项目结构设置 1.Platform——Settings 项目结构---SDKS 2.Project——SDK 3.Modules——SDK——Sources 4.Modules——SDK——Dependencies 二、设置--编译--字节码版本 Settings——Build,——Java Compiler

【linux】校招中的“熟悉linux操作系统”一般是指达到什么程度?

这样&#xff0c;你先在网上找一套完整openssh升级方案&#xff08;不是yum或apt的&#xff0c;要源码安装的&#xff09;&#xff0c;然后在虚拟机上反复安装测试&#xff0c;直到把他理解了、背下来。 面试的时候让你简单说说linux命令什么的&#xff0c;你就直接把这个方案…

Linux 网络配置及基础服务

目录 一. 查看网络配置信息的相关命令 1.1 ifconfig 命令 作用 1&#xff1a; 作用 2&#xff1a; 拓展&#xff1a; 1.2 ip/ethtool命令 1.3 hostname命令 1.4 route 命令 1.5 netstat 命令 1.6 ss&#xff08;socket statistics&#xff09;命令 1.7 ping 命令 …

接口自动化处理动态参数

接口自动化处理动态参数 1、流程说明 某些接口的请求入参数据不能写死&#xff0c;需要动态传参。如用户注册接口&#xff0c;用户名需要动态生成。使用yaml编写测试数据时&#xff0c;在需要动态参数的数据后面添加上特殊字符${生成动态数据的方法名&#xff08;参数&#x…

网工内推 | 港企、合资公司,厂商认证优先,五险一金

01 九龙仓&#xff08;长沙&#xff09;置业有限公司 招聘岗位&#xff1a;IT网络工程师 职责描述&#xff1a; 1.负责公司网络架构规划设计、设备选型、远程组网方案的规划和设计&#xff1b; 2.负责公司网络IP地址规划管理&#xff0c;根据业务需求和公司状况&#xff0c;对…

推荐收藏!算法工程师面试常考的手撕面试题!

今天给大家分享一些算法工程师技术面试中常手撕的代码。 不管是秋招还是社招&#xff0c;互联网大厂的技术面试中的手撕代码这一部分总是绕不过去的一关。 如果你对这些感兴趣&#xff0c;可以文末找我们交流 手撕 numpy写线性回归的随机梯度下降&#xff08;stochastic gra…

微信小程序 安卓/IOS兼容问题

一、背景 在开发微信小程序时&#xff0c;不同的手机型号会出现兼容问题&#xff0c;特此记录一下 二、安卓/IOS兼容问题总结 2.1、new Date()时间转换格式时&#xff0c;IOS不兼容 问题&#xff1a;在安卓中时间格式2024-1-31 10:10:10&#xff0c;但是在iOS中是不支持 &q…

模板讲解之进阶

在之前的C入门的博客中我们就学习到了模板初阶&#xff0c;今天我们来学习模板的进阶&#xff0c;以便于更好地将模板运用到代码中 非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的…

Java基础 集合(五)Set详解

目录 简介 set种类 AbstractSet 抽象类 SortedSet 接口 HashSet LinkedHashSet TreeSet 前言-与正文无关 生活远不止眼前的苦劳与奔波&#xff0c;它还充满了无数值得我们去体验和珍惜的美好事物。在这个快节奏的世界中&#xff0c;我们往往容易陷入工作的漩涡&#xff…

【2024.2.2练习】方格取数

题目描述 题目思路 如果从A到B只走一次的话可以用动态规划轻松解决。问题在于会走两次&#xff0c;第二次显然要走获取数字最多的路径&#xff0c;但第一次走哪条路径需要抉择。 错误的思路是以为这道题适合贪心&#xff0c;两次都选择最优路线。可以举出反例。 A2112112B …

Vim编辑器

1.文件复制 拷贝/etc/profile 数据到/root 目录下 cp /etc/profile /root如果root文件夹在上一目录下 cp /etc/profile ../root 2.打开文件 vim etc/profile 打开ect文件夹中的profile文件 3.文件编辑 文件编辑分为一般模式 与编辑模式。打开文件为一般模式&#xff0c;按…

【c语言】strcpy()和strncpy():字符串复制详解

&#x1f388;个人主页&#xff1a;甜美的江 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;c语言 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步&a…

C++ copy()函数详细介绍

copy() 是一个标准库函数&#xff0c;位于 头文件中。它用于将一个容器中的元素复制到另一个容器中&#xff0c;或者将一个范围内的元素复制到另一个范围中。 函数参数介绍 copy( first, last, d_first );first 和 last&#xff1a;表示输入范围的迭代器。 first 指向要复制的…

操作系统基础:内存管理概述【中】

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;OS从基础到进阶 &#x1f3dd;️1 基本分页存储管理&#x1f3d6;️1.1 总览&#x1f3d6;️1.2 什么是分页存储&#x1f3f0;1.2.1 将物理空间分页&#x1f3f0;1.2.2 将逻辑空间分页&…

Android搭建python环境

通过wifi连接adb&#xff1a; 首先下载无线abd工具&#xff1a; https://www.downkuai.com/android/170494.html 运行效果图&#xff1a; 然后开启后根据自身ip即可连接&#xff1a; adb connect ip:5555 安装busybox: 首先执行如下命令查看手机架构&#xff1a; adb sh…

分布式ID介绍实现方案总结

分布式 ID 介绍 什么是 ID&#xff1f; 日常开发中&#xff0c;我们需要对系统中的各种数据使用 ID 唯一表示&#xff0c;比如用户 ID 对应且仅对应一个人&#xff0c;商品 ID 对应且仅对应一件商品&#xff0c;订单 ID 对应且仅对应一个订单。 我们现实生活中也有各种 ID&…

[C++]继承(续)

一、基类和派生类对象赋值转换 在public继承时&#xff0c;父类和子类是一个“is - a”的关系。 子类对象赋值给父类对象/父类指针/父类引用&#xff0c;我们认为是天然的&#xff0c;中间不产生临时对象&#xff0c;也叫作父子类赋值兼容规则&#xff08;切割/切片&#xff…

Spring-mybatis

怎样通过Spring整合Mybatis来实现业务 目录 1.导入依赖 <dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.1</version><scope>test</scope></dependency>&l…

化工企业能源在线监测管理系统,能源管理新利器

化工企业在开展化工生产活动时&#xff0c;能源消耗量较大&#xff0c;其节能潜力空间也较大&#xff0c;因此必须控制能耗强度&#xff0c;促进能效水平的稳步提升。化工企业通过能源现状的分析&#xff0c;能够实现能源使用情况的实时反馈与监管&#xff0c;从而达到节能减排…

MobPush:Android SDK 集成指南

开发工具&#xff1a;Android Studio 集成方式&#xff1a;Gradle在线集成 安卓版本支持&#xff1a;minSdkVersion 19 集成前准备 注册账号 使用PushSDK之前&#xff0c;需要先在MobTech官网注册开发者账号&#xff0c;并获取MobTech提供的AppKey和AppSecret&#xff0c;…