当前位置: 首页 > news >正文

算法日记---二分查找

目录

前言

一、二分查找

1.思想

2.简单二分

3.优点

4.局限性

二、模板

1.基本模板

2.简单例题(LeetCode)

4.有重复元素的二分

5.0-1问题

总结


前言

本文通过讲解简单的二分查找配合leetcode例题对二分查找本质、模板进行了基础的总结


提示:以下是本篇文章正文内容,下面案例可供参考

一、二分查找

1.思想

二分查找本质是折半查找思想,每一轮查找的范围是上一轮的一半,每次查找比较中间元素的值和目标值的大小。比较结论决定去哪一边作为下一轮的查找范围,循环直到查找范围为空。

2.简单二分

简单二分指的是数组不包含重复的元素,重点关注每次判边的逻辑。

3.优点

速度快 O(logn)且不需要额外的空间

4.局限性

  • 有序:需要保证每次折半都有意义
  • 数组:数组寻址的复杂度是 O (1)。如果是用链表存储一串数,二分查找是无意义的。链表的寻址是 O (n)。

二、模板

1.基本模板

二分查找的模板很多样,开闭的条件不同,判断的条件也不同

最本质的问题是当前判断结束后,下一次要取左半边还是右半边缩小区间的条件是什么?判断的条件是什么 

注意:

  1. left<=right:实际进入循环时,left和right只向的元素不会进行if条件判断。比如left=right时,加此等号,才会对二者所指元素进行判断。否则不会。
  2. mid =(left+right)>>>1:因为java会把最高位看成符号位,如果两个数过大,可能会导致数据溢出问题。我们需要通过无符号右移运算符来避免这种问题。还有一种写法是mid=left+(right-left)/2
  3. 更新left和right,避免死循环
public static int binarySerach(int[] a,int target){int left=0;int right=a.length-1;while(left<=right){int mid=(left+right)>>>1;if(target<a[mid]){//目标数在左边right=mid-1;}else if(a[mid]<target){//目标书在右边left=mid+1;}else{return mid;}}return -1;
}

2.简单例题(LeetCode)

1.704. 二分查找(基本简单)

class Solution {public int search(int[] nums, int target) {int left=0;int right=nums.length-1;while(left<=right){int mid=(left+right)>>>1;if(nums[mid]<target){left=mid+1;}else if(nums[mid]>target){right=mid-1;}else{return mid;}}return -1;}
}

2.744.寻找比目标字母大的最小字母

class Solution {public char nextGreatestLetter(char[] letters, char target) {int left=0;int right=letters.length-1;while(left<=right){int mid=(left+right)>>>1;if(letters[mid]<=target){left=mid+1;}else{right=mid-1;}}return left<letters.length?letters[left]:letters[0];}
}

3.74. 搜索二维矩阵

class Solution {public boolean searchMatrix(int[][] matrix, int target) {int left=0;//使用m*n将二维转为一维int m=matrix[0].length;int n=matrix.length;int right=m*n-1;while(left<=right){int mid=(left+right)>>>1;int x=matrix[mid/m][mid%m];//把mid元素转换回原二维矩阵的位置if(x==target){return true;}else if(x>target){right=mid-1;}else{left=mid+1;}}return false;}
}

4.162. 寻找峰值

此题究其本质依然是二分查找,我们需要根据题目重要条件进行理解

“你可以假设 nums[-1] = nums[n] = -∞ 。”

以下做法需要注意边界情况的判断

class Solution {public int findPeakElement(int[] nums) {int n = nums.length;if (n == 1) {return 0;}int left = 0, right = n - 1;while (left <= right) {int mid = left + (right - left) / 2;// 边界情况判断:数组第一个元素是峰值(只有它自己,或者比下一个大)if (mid == 0) {if (nums[mid] > nums[mid + 1]) {return mid;}}// 边界情况判断:数组最后一个元素是峰值(只有它自己,或者比前一个大)else if (mid == n - 1) {if (nums[mid] > nums[mid - 1]) {return mid;}}// 中间位置是峰值(比左右都大)else if (nums[mid] > nums[mid - 1] && nums[mid] > nums[mid + 1]) {return mid;}// 判断峰值在左侧还是右侧if (nums[mid] < nums[mid + 1]) {// 右侧有上升趋势,峰值在右侧left = mid + 1;} else if (nums[mid] < nums[mid - 1]) {// 左侧有上升趋势,峰值在左侧right = mid - 1;}}// 理论上不会走到这里,因为题目保证有解return -1;
}}

题解中的做法是无需单独判断边界情况的,也没有主动对mid元素与左右两元素进行比较

因为左右边界都是负无穷,找到往递增的一边方向找就一定能找到峰值(图中白圈内区域)。这就代表着 只要数组中存在一个元素比相邻元素大,那么沿着它一定可以找到一个峰值。

特点:一分为二后,一侧是有序数组,另一侧是循环数组。

根据这个特点,先判断有序数组、循环数组分别在哪一侧,再判断 target 在有序数组 还是 循环数组中

判断方法是让 target 和有序数组的首尾做比较,看是否在有序数组中。

1. 3. 搜索旋转排序数组

class Solution {public int search(int[] nums, int target) {int left=0;int right=nums.length-1;while(left<=right){int mid=(left+right)>>>1;if(nums[mid]==target){return mid;}//左半部分有序if(nums[left]<=nums[mid]){//目标值在左半部分区间内if(nums[left]<=target && target<nums[mid]){right=mid-1;}else{left=mid+1;//缩小范围到右半部分区间}}// 右半部分是有序的else {// 目标值在右半部分有序区间内if (nums[mid] < target && target <= nums[right]) {left = mid + 1;} else {right = mid - 1;//缩小范围到左半部分区间}}}return -1;}}

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

不多说了,都在注解里:

最主要的还是需要判断目标值到底是在mid的左边还是右边,才能进行下一步二分。而判断依据就是根据两端递增区间的特性将nums[mid]和nums[right]进行比较。这也就是开篇说的判断取左半段还是右半段最重要的条件。

class Solution {public int findMin(int[] nums) {int left=0;int right=nums.length-1;while(left<=right){int mid=(left+right)>>>1;//首先由题可知,数组一定被分为了左右两个递增区间(或者没分,也就是0次旋转),且第一段所有元素大于第二段所有元素//需要比较nums[mid]和nums[right]的关系。//如果<=,意味着nums[mid]一定处于第二个递增区间,最小值要么是该元素,要么就在该元素左侧//如果>,意味着nums[mid]处于第一个递增区间。最小值一定在该元素右侧if(nums[mid]>nums[right]){//mid处于左半段left=mid+1;}else{//mid处于右半段if(mid==0||nums[mid]<=nums[mid-1]){//mid就是最小值,注意mid=0时索引越界问题return nums[mid];}else{//mid处于右半段,但最小值在mid的左边right=mid-1;}}}return -1;}
}

4.有重复元素的二分

通常要求找出第一个目标元素或是最后一个目标元素

存在重复元素的二分查找,无非就是多加了一个判断,判断是否为第一个/最后一个。

以查询第一个目标元素为例,判断条件就是看mid-1是否等于target,如果不是,那么mid就是我要

找的数了。这依然是二分关键。

public class BinarySearch {public static int binarySearch(int[] nums, int target) {int left = 0;int right = nums.length - 1;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 {// 当找到目标值时,判断是否是第一个出现的位置if (mid == 0 || nums[mid - 1] != target) {//是第一个元素,注意索引越界问题。return mid;} else {right = mid - 1;}
//以下为找最后一个目标元素的判断条件
//if (mid == nums.length-1 || nums[mid+1]!=target){
//}else{
// left=mid+1;}}}return -1;}public static void main(String[] args) {int[] nums = {1, 3, 8, 8, 8, 9};int target = 8;int result = binarySearch(nums, target);System.out.println("第一个目标值 " + target + " 的索引是: " + result);}
}

5.0-1问题

点到为止,下期再见。


总结

本文对二分查找进行简单讲解,还有比较常见的二分查找应用“0-1问题”留着下期讲解。

二分的模板多种多样,但究其本质最重要的还是如何判断下一次二分的区间,取左边还是取右边。这个就是需要根据不同题目思考的点了


文章转载自:

http://2cZ2iFOC.cknsx.cn
http://raMhIfNd.cknsx.cn
http://QKpLB3n4.cknsx.cn
http://k9Z5g2DW.cknsx.cn
http://RDb1PCRx.cknsx.cn
http://AeXGDoeY.cknsx.cn
http://i9jpfVHh.cknsx.cn
http://MqUt308m.cknsx.cn
http://k6EexEGo.cknsx.cn
http://aE1477Qr.cknsx.cn
http://YopyVYEI.cknsx.cn
http://aDgBVSgm.cknsx.cn
http://Y3E1kyVB.cknsx.cn
http://zwBioZIG.cknsx.cn
http://nLpjbPtm.cknsx.cn
http://H6BMmeg8.cknsx.cn
http://jYnZhpEk.cknsx.cn
http://Bp19M7TJ.cknsx.cn
http://ZPCjll5c.cknsx.cn
http://z2gAfFkm.cknsx.cn
http://cgvIdjQZ.cknsx.cn
http://Flnmp6zT.cknsx.cn
http://VTtGkcuQ.cknsx.cn
http://7cwP8dKg.cknsx.cn
http://w2ZeXZrm.cknsx.cn
http://khKXRXR2.cknsx.cn
http://hkTEZUjh.cknsx.cn
http://Ench8LXZ.cknsx.cn
http://KgBFZSbQ.cknsx.cn
http://VcEVNofV.cknsx.cn
http://www.dtcms.com/a/383318.html

相关文章:

  • Pandas模块
  • 在Unity2021中使用Profiler的Deep Profile功能时内存超高怎么办?
  • GooseDB,一款实现服务器客户端模式的DuckDB
  • openEuler部署Samba服务器:实现跨平台文件共享
  • 认知语义学的象似性原理对人工智能自然语言处理深层语义分析的影响与启示
  • 【Linux】线程池模拟
  • TensorRT 10.13.3: Limitations
  • RK3568编写自启动脚本
  • AI 伦理争议背后:算法偏见如何产生?又该如何规避?
  • C++ 中使用 iterator 中注意事项和优化技巧(2)
  • 【MySQL|第八篇】事务与索引
  • OD C卷 - 小明找位置
  • JavaScript与jQuery:从入门到面试的完整指南
  • 最长上升子序列(LIS)全解析:从基础到进阶(基础讲解篇)
  • 海盗王64位dx9客户端修改篇之七
  • 【c++进阶系列】:map和set的模拟实现(附模拟实现的源码)
  • Redis的RedLock
  • AutoGen——自定义Agent
  • 第5节-连接表-Natural-Join
  • CentOS Docker 环境下安装 HertzBeat 并配置 VictoriaMetrics 时序数据库指南
  • 【Linux】 存储分级的秘密
  • GitAgent-面壁智能联合清华大学发布的大模型智能体应用框架
  • 《基于国产Linux的机房终端安全重构方案》
  • JavaWeb-Servlet总结及JSP
  • 《黑神话:悟空》Xbox版本性能模式画质分析
  • 支持向量机:从理论到实践
  • 软件体系结构——发展脉络
  • 【C++】队列queue的使用
  • 对网络通信领域的“活化石”与“瑞士军刀”—— `telnet`
  • 迭代器和生成器的区别与联系