算法刷题入门数据结构|二分查找

news2024/11/19 15:18:01

一.二分查找基础

1、二分查找介绍

二分查找(Binary search)也称折半查找,是一种效率较高的查找方法,时间复杂度。当对查数题目有时间复杂度要求是,首先就要考虑到二分查找。二分查找的思想很简单,属于分治策略的变种情况。但是,二分查找要求线性表中的记录必须是有序的集合,每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为 0,所以必须采用顺序存储。

2、二分查找演示

下面是二分查找与顺序查找的演示图对比:

 

可以看出 二分查找 在查找数字 3 时只需3次,而 顺序查找 在查找3时需要3次。

3、二分查找原理

二分查找算法的原理如下:

假设在[begin,end)范围内搜索某个元素 v,mid == (begin + end)/ 2,end指的是数组的长度。

比较v与arr[mid],有以下三种情况:

1)若 v < arr[mid],则high = mid - 1;继续查找在左半区间进行;

2)若 v > arr[mid],则low = mid + 1;继续查找在右半区间进行;

3)若 v = arr[mid],则查找成功,返回 mid 值;

 

总结:如果大家有学习过二叉树知识,其实二分查找原理就是来自于二叉查找树(即:二叉平衡树),所以查找的最大次数就是二叉树深度。

4、二分查找性能

  二分查找最好时间复杂度是:最好情况下只需要进行1次比较就能找到目标元素;

 二分查找最坏时间复杂度是:最坏情况就是查找不到目标元素,所需的时间复杂度可借助该序列的二叉树(二分查找判定树)形式进行分析:

 序列[4,13,14,15,34,40,45,47,48,49,55]可以构建成下图所示的二叉树,以及查找对应的值14的过程图如下:

 

具有n个结点的二分查找树的深度为

,因此,查找成功时,所进行的关键码比较次数至多为
。而查找失败时和目标元素进行比较的次数最多也不超过树的深度
,因此最坏时间复杂度时
。二分查找平均时间复杂度是

5.代码模板

 1 int binarySearch(int[] nums, int target) {
 2     int left = 0; 
 3     int right = nums.length - 1; // 注意
 4     while(left <= right) {
 5         int mid = left + (right - left) / 2;
 6         if(nums[mid] == target)
 7             //这里根据具体情况设定
 8         else if (nums[mid] < target)
 9             left = mid + 1; // 注意
10         else if (nums[mid] > target)
11             right = mid - 1; // 注意
12     }
13 //其他特殊情况处理,主要是左右边界情况
14 }

二.常见题型

题型一:寻找一个数(基本的二分搜索) 

比如我们给定数组如下动态图中,我们需要查找等于673的元素,如果存在,返回其索引,否则返回 -1。[left, right]

 

 1 int binarySearch(int[] nums, int target) {
 2     int left = 0; 
 3     int right = nums.length - 1; // 注意
 4     while(left <= right) {
 5         int mid = left + (right - left) / 2;
 6         if(nums[mid] == target)
 7             return mid; 
 8         else if (nums[mid] < target)
 9             left = mid + 1; // 注意
10         else if (nums[mid] > target)
11             right = mid - 1; // 注意
12     }
13     return -1;
14 }

题型二:寻找左侧边界的二分搜索

比如我们给定数组1,2,3,4,4,4,5,6,7,7,8,9,我们需要查找第一个等于4的元素,「搜索区间」是[left, right]

 1 int left_bound(int[] nums, int target) {
 2     int left = 0, right = nums.length - 1;
 3     while (left <= right) {
 4         int mid = left + (right - left) / 2;
 5         if (nums[mid] < target) {
 6             left = mid + 1;
 7         } else if (nums[mid] > target) {
 8             right = mid - 1;
 9         } else if (nums[mid] == target) {
10             // 别返回,锁定左侧边界
11             right = mid - 1;
12         }
13     }
14     // 最后要检查 left 越界的情况
15     if (left >= nums.length || nums[left] != target)
16         return -1;
17     return left;
18 }

题型三:寻找右侧边界的二分查找

比如我们给定数组1,2,3,4,4,4,5,6,7,7,8,9,我们需要查找最后一个等于4的元素,「搜索区间」是[left, right]

 1 int right_bound(int[] nums, int target) {
 2     int left = 0, right = nums.length - 1;
 3     while (left <= right) {
 4         int mid = left + (right - left) / 2;
 5         if (nums[mid] < target) {
 6             left = mid + 1;
 7         } else if (nums[mid] > target) {
 8             right = mid - 1;
 9         } else if (nums[mid] == target) {
10             // 别返回,锁定右侧边界
11             left = mid + 1;
12         }
13     }
14     // 最后要检查 right 越界的情况
15     if (right < 0 || nums[right] != target)
16         return -1;
17     return right;
18 }

题型四:查找第一个大于给定值的元素

比如我们给定数组1,2,3,4,4,4,5,6,7,7,8,9,15,26,34,45,我们随便输入一个值,这个值可以是数组里面的值,也不可不在数组里面,查找出第一个比给定值大的元素。

 1    /**
 2      * 查找第一个大于给定值的元素
 3      *
 4      * @param nums   数组
 5      * @param value  给定的值
 6      * @return
 7      */
 8     private static int sercFirstOverVlaue(int[] nums, int value) {
 9         int low = 0;
10         int high = nums.length - 1;
11         while (low <= high) {
12             int mid = low + ((high - low) >> 1);
13             if (nums[mid] > value) {
14                 // 判断当前是第一个元素或者前一个元素小于等于给定值,则返回下标,如果前一个元素大于给定的值,则继续往前查找。
15                 if ((mid == 0) || nums[mid - 1] <= value) return mid;
16                 else high = mid - 1;
17             } else {
18                 low = mid + 1;
19             }
20         }
21         return -1;
22     }

三.扩展题型(来自leetcode) 

题目一:240. 搜索二维矩阵 II

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:

每行的元素从左到右升序排列。

每列的元素从上到下升序排列。

示例 1:

 

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5

输出:true

示例 2:

 

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20

输出:false

思路

这道题的关键在于得找到特殊位置的起始点,从该起始点,可以分治地去查找待查找地点。

主对角线上左上角和右下角不符合条件,因为

左上角的点,其周围的点都比它大,没法找到合适的查找路径

右下角的点,其周围的点都比它小,没法找到合适的查找路径

次对角线上左下角和右上角的点更合适,因为

左下角的点:往上比它小,往右比它大

右上角的点:往左比它小,往下比它大

因此,选择左下角或右上角的点都合适。

 1 public boolean searchMatrix(int[][] matrix, int target) {
 2     int n = matrix.length
 3     int m = matrix[0].length;
 4     int i = 0, j = m - 1; // 从右上角开始走
 5     while(i < n && j >= 0) { // 最多会走到左下角去
 6         int a = matrix[i][j];
 7         if (a == target) return true;
 8         if (a < target) i++; // 排除掉当前这一行, 往下走
 9         else j--; // 排除掉当前这一列, 往左走
10     }
11     return false;
12 }

题目二:4. 寻找两个正序数组的中位数

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

算法的时间复杂度应该为 O(log (m+n)) 。

示例 1:

输入:nums1 = [1,3], nums2 = [2]

输出:2.00000

解释:合并数组 = [1,2,3] ,中位数 2

示例 2:

输入:nums1 = [1,2], nums2 = [3,4]

输出:2.50000

解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

解法一:直接找中位数

我们不需要将两个数组真的合并,我们只需要找到中位数在哪里就可以了。

 1 public double findMedianSortedArrays(int[] A, int[] B) {
 2     int m = A.length;
 3     int n = B.length;
 4     int len = m + n;
 5     int left = -1, right = -1;
 6     int aStart = 0, bStart = 0;
 7     for (int i = 0; i <= len / 2; i++) {
 8         left = right;
 9         if (aStart < m && (bStart >= n || A[aStart] < B[bStart])) {
10             right = A[aStart++];
11         } else {
12             right = B[bStart++];
13         }
14     }
15     if ((len & 1) == 0)
16         return (left + right) / 2.0;
17     else
18         return right;
19 }

空间复杂度:我们申请了常数个变量,也就是m,n,len,left,right,aStart,bStart 以及 i。时间复杂度:遍历 len/2+1 次,len=m+n,所以时间复杂度依旧是 O(m+n)O(m+n)。

总共 8 个变量,所以空间复杂度是 O(1)O(1)

解法二:二分查找

我们一次遍历就相当于去掉不可能是中位数的一个值,也就是一个一个排除。求两个有序数组的中位数,可以变换为,求第k个数。由于两个数组有序,我们每次只要从两个数组中,分别从左取k/2个数,然后想象尝试将这2组k/2个数合并。若第1组的最后一个数,小于第2组的最后一个数,则第1组不可能作为第k个数,那么可以将第1组的k/2个数全部排除。时间复杂度 O ( l o g ( m + n ) ) ,空间复杂度 O ( 1 ) 。

 1 class Solution {
 2     public double findMedianSortedArrays(int[] nums1, int[] nums2) {
 3         int n = nums1.length, m = nums2.length;
 4         int left = (n + m + 1)>>1;
 5         int right = (n + m + 2)>>1;
 6         int median1 = getKth(nums1, nums2, left);
 7         int median2 = getKth(nums1, nums2, right);
 8         return (median1 + median2) / 2.0;
 9     }
10 
11     private int getKth(int[] nums1, int[] nums2, int k) {
12         int n = nums1.length, m = nums2.length;
13         int i = 0, j = 0;
14         while (i < n && j < m && k > 1) {
15             int ie = Math.min(i + k>>1 - 1, n - 1);
16             int je = Math.min(j + k>>1 - 1, m - 1);
17             if (nums1[ie] <= nums2[je]) {
18                 k -= (ie - i + 1); // 更新k
19                 i = ie + 1; // 更新i, 注意不要先更新i, 再更新 k
20             } else {
21                 k -= (je - j + 1);
22                 j = je + 1;
23             }
24         }
25         if (i >= n) return nums2[j + k - 1];
26         if (j >= m) return nums1[i + k - 1];
27         return Math.min(nums1[i], nums2[j]);
28     }
29 }

题目三:33. 搜索旋转排序数组 

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

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

输出:4

示例 2:

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

输出:-1

示例 3:

输入:nums = [1], target = 0

输出:-1

解题思路:二分查找

nums[0] <= nums[mid](0 - mid不包含旋转)且nums[0] <= target <= nums[mid] 时 high 向前规约;

nums[mid] < nums[0](0 - mid包含旋转),target <= nums[mid] < nums[0] 时向前规约(target 在旋转位置到 mid 之间)

nums[mid] < nums[0],nums[mid] < nums[0] <= target 时向前规约(target 在 0 到旋转位置之间)

其他情况向后规约

也就是说nums[mid] < nums[0],nums[0] > target,target > nums[mid] 三项均为真或者只有一项为真时向后规约。

 1 public int search(int[] nums, int target) {
 2     if(nums.length==0){
 3         return -1;
 4     }
 5     if(nums.length==1){
 6         return nums[0]==target? 0:-1;
 7     }
 8     int n=nums.length;
 9     int i=0;
10     int j=n-1;
11     while(i<=j){
12         int mid=i+(j-i)/2;
13         if(nums[mid]==target){
14             return mid;
15         // 如果mid在左边, 注意nums[mid]可能等于nums[0]
16         }else if(nums[0]<=nums[mid]){
17             // target也在左边
18             if(nums[0]<=target && target<nums[mid]){
19                 j=mid-1;
20             }else{
21                 i=mid+1;
22             }
23         // 如果mid在右边
24         }else if(nums[mid]<nums[0]){
25             // target也在右边
26             if(nums[mid]<target && target<=nums[n-1]){
27                 i=mid+1;
28             }else{
29                 j=mid-1;
30             }
31         }
32     }
33     return -1;
34 }

四.总结

我想大家对二分查找算法题目会遇到很多的写法,比如While判断逻辑中存在左闭右闭区间 [left,right]或左闭右开区间 [left,right)场景。不管是那种情况,都需要考虑边界条件,大家只需要考虑某一种判断逻辑即可,不需要全部记忆,增加负担。

1、二分查找算法具有三个作用:搜索目标值,搜索目标值的左边界(序列中最大的小于目标值的元素),搜索目标值的右边界(序列中最小的大于目标值的元素)。

2、while循环的判断条件和搜索区间右边界right的赋值方式与right的初始化取值有关。right初始化为序列长度,则判断条件为left<right,赋值方式为right=mid;right初始化为序列长度-1,则判断条件为left<=right,赋值方式为right=mid-1。

3、二分查找三个作用的实现取决于while循环中对“序列中位数==目标值”这一情况的处理:

 1 1、搜索目标值
 2 if(nums[mid]==target)
 3     return mid;
 4 
 5 2、搜索目标值的左边界
 6 if(nums[mid]==target)
 7     right=mid;  //最后结果左边界=right-1
 8 或者
 9 if(nums[mid]==target)    
10     right=mid-1;//最后结果左边界=right
11 
12 3、搜索目标值的右边界
13 if(nums[mid]==target)
14     left=mid+1;

我这里只给了大家<=的情况,大家如果不适应,可以留言给大家提供<的情况代码。

更多精彩关注wx公众号:

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

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

相关文章

贷后催收评分模型中的数据清洗与数据治理细节介绍

数据清洗是一个非常修炼身心的过程&#xff0c;途中你除了需要把所有的数据整业务合到一张宽表里。而这种宽表中所有的字段&#xff0c;是你理解完业务后&#xff0c;细心整理出来的所有适合建模的数据。 今天我们给大家介绍一下&#xff0c;在风控贷后评分模型中&#xff0c;…

C规范编辑笔记(七)

往期文章&#xff1a; C规范编辑笔记(一) C规范编辑笔记(二) C规范编辑笔记(三) C规范编辑笔记(四) C规范编辑笔记(五) C规范编辑笔记(六) 正文&#xff1a; 大家好&#xff0c;今天来分享一下C语言规范编辑笔记的第七篇&#xff0c;分享这个是希望自己后面忘记了可以去复习…

ADI Blackfin DSP处理器-BF533的开发详解26:扩展IO输入的详细讲解(含源代码)

硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 硬件设计原理图 功能介绍 ADSP-EDU-BF53x 开发板上扩展接口的 PORT2 和 PORT3 中引出了 6 个扩展 IO 接口输入接口&#xff0c;这些连接到了CPLD…

C. Rooks Defenders Codeforces Round #791 (Div. 2)(树状数组!)

传送门 题意&#xff1a;给你一个的棋盘&#xff0c;然后给你一个t&#xff08;t只能为1&#xff0c;2&#xff0c;3&#xff09;&#xff0c;对于不同的t产生不同的影响&#xff1a; t1时&#xff0c;给你一个点的坐标x,y&#xff0c;在这个点上生成一辆坦克&#xff08;保证…

模拟实战从外网打点渗透到内网域控的笔记

信息收集 本次项目是一个是模拟渗透测试 电信诈骗网站&#xff0c;境外人员依赖该网站通过优惠卷诱导受害者进行消费&#xff0c; 诈骗受害人金钱。 前台地址 项目拓扑图 http://ip/user.php?moddo&actlogin&fromtohttp%3A%2F%2F43.143.193.216%2F 后台地址 http…

rabbitmq基础2——rabbitmq二进制安装和docker安装、基础命令

文章目录一、RabbitMQ安装1.1 二进制安装1.2 rabbitmqctl工具1.3 docker安装二、rabbitmq基础命令2.1 多租户与权限类2.1.1 创建虚拟主机2.1.2 查看虚拟主机信息2.1.3 删除虚拟主机2.1.4 给用户授权2.1.5 清除用户权限2.1.6 查看权限2.2 用户管理类2.2.1 创建用户2.2.2 查看用户…

爱心源码动图-Html网页运行

程序示例精选 爱心源码动图-Html网页运行 如需安装运行环境或远程调试&#xff0c;见文章底部微信名片&#xff01; 前言 Html写的追女生神器-爱心动图&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读&#xff0c;对学习与使用Html有较好的帮助。 文章目录 一、所需工具…

Redis高可用之主从复制、哨兵、cluster集群

Redis高可用之主从复制、哨兵、cluster集群Redis 高可用什么是高可用Redis的高可用技术Redis主从复制主从复制的作用主从复制流程搭建Redis主从复制所有节点安装Redis修改master节点的配置文件修改slave节点的配置文件验证主从效果Redis哨兵模式哨兵模式的作用哨兵结构故障转移…

Redis集群模式

目录 前言 一、集群的作用 二、集群模式的数据分片 三、集群模式的主从复制模型 四、Redis集群模式 Redis集群部署 开启群集功能 修改所有集群服务的配置文件端口&#xff0c;使其不一致 启动集群 集群测试 前言 1、集群&#xff0c;即 Redis Cluster&#xff0c; …

模型效果差?我建议你掌握这些机器学习模型的超参数优化方法

模型优化是机器学习算法实现中最困难的挑战之一。机器学习和深度学习理论的所有分支都致力于模型的优化。 机器学习中的超参数优化旨在寻找使得机器学习算法在验证数据集上表现性能最佳的超参数。超参数与一般模型参数不同&#xff0c;超参数是在训练前提前设置的。举例来说&a…

CKA考试Tips

前言 今年黑五的双证套餐的折扣比双11时还便宜个200多,不到2000&#xff0c;应该是史低吧,反正比前年低。即使考试前看了各种避坑技巧&#xff0c;虽然通过了但是结果还是因为各种问题导致时间不够没做完扣分&#xff0c;于是下面总结一下参加CKA/CKS考试时候的技巧。 报名及考…

[基因遗传算法]进阶之四:实践VRPTW

参考资料: 《旅行商问题(TSP)、车辆路径问题(VRP,MDVRP,VRPTW)模型介绍》 本文对《基于GA算法解决VRPTW》的分析和思考.具体的代码可以参考 《Python实现(MD)VRPTW常见求解算法——遗传算法&#xff08;GA&#xff09;》 . 文章目录壹、VRPTW一. 定义类二、数据读取三. 构造初…

JVM调优手段

JDK提供命令工具 jstat 是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据&#xff0c;在没有 GUI图形界面&#xff0c;只提供了纯文本控制台环境的服务器上&#xff0c;它将是运行期定位虚拟…

博球一看,CSDN与你共观世界杯

2022卡塔尔世界杯不知不觉已接近尾声&#xff0c;不仅让人感叹&#xff0c;乌拉圭&#xff0c;巴西&#xff0c;葡萄牙都已淘汰&#xff0c;四强诞生分别是阿根廷&#xff0c;法国&#xff0c;摩洛哥&#xff0c;克罗地亚&#xff0c;非常期待梅西和魔笛的对决&#xff0c;也希…

电子学会2020年12月青少年软件编程(图形化)等级考试试卷(三级)答案解析

目录 一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 二、判断题&#xff08;共10题&#xff0c;每题2分&#xff0c;共20分&#xff09; 三、编程题【该题由测评师线下评分】&#xff08;共3题&#xff0c;共30分&#xff09; 青少年软件…

【云计算与大数据技术】虚拟化简介及虚拟化的分类讲解(图文解释 超详细)

一、虚拟化简介 1&#xff1a;什么是虚拟化 虚拟化是指计算机元件在虚拟的基础上而不是在真实的、独立的物理硬件基础上运行。这种以优化资源、简化软件的重新配置过程为目的的解决方案就是虚拟化技术 虚拟化架构就是在一个物理硬件机器上同时运行多个不同应用的独立的虚拟系…

r语言中对LASSO回归,Ridge岭回归和弹性网络Elastic Net模型实现|视频

Glmnet是一个通过惩罚最大似然关系拟合广义线性模型的软件包。正则化路径是针对正则化参数λ的值网格处的lasso或Elastic Net&#xff08;弹性网络&#xff09;惩罚值计算的。 最近我们被客户要求撰写关于LASSO的研究报告&#xff0c;包括一些图形和统计输出。该算法非常快&am…

电子学会2020年12月青少年软件编程(图形化)等级考试试卷(四级)答案解析

目录 一、单选题&#xff08;共15题&#xff0c;每题2分&#xff0c;共30分&#xff09; 二、判断题&#xff08;共10题&#xff0c;每题2分&#xff0c;共20分&#xff09; 三、编程题【该题由测评师线下评分】&#xff08;共4题&#xff0c;共50分&#xff09; 青少年软件…

51单片机——LED 点阵点亮一个点,小白详解

LED点阵介绍&#xff1a; LED点阵是由发光二极管排列组成的显示器件&#xff0c;在我们生活中的电器中随处可见&#xff0c;被广泛用于汽车报站器&#xff0c;广告屏等。 通常用用较多的是8*8点阵&#xff0c;然后使用多个8*8点阵组成不同分辨率的LED点阵显示屏&#xff0c;比如…

kubernetes--kube-proxy组件深入理解

文章目录kube-proxy的工作原理netfilter的运行机制ipvs和iptables有什么区别&#xff1f;iptables在网络栈的hook点更多&#xff0c;而ipvs的hook点很少iptables的hook点ipvs的hook点如何切换&#xff1f;ipvs安装为何推荐ipvs&#xff1f;为什么iptables或者ipvs在每个节点上都…