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

二分查找算法(一)

一、力扣704——二分查找

1.暴力解法:直接for遍历一遍数组,找到值就返回下标,未找到,返回-1。O(N)

2.二分查找: 具有二段性,根据某规律,能将数组分成两部分,能够砍掉数组的一部分

暴力解法的缺陷,每次查找时,只能消除掉其中的一个数,利用二分查找可以砍掉一部分。

 

代码如下:

class Solution {
public:int search(vector<int>& nums, int target) {int n = nums.size();int left = 0, right = n - 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 {return mid;}}return -1;}
};

二、力扣34——在排序数组中查找元素的第一个和最后一个位置

 

题目解析

非递减顺序:表示要么是递增的,要么是平衡的(数值相同),找到就返回值所在的区间,找不到就返回[-1, -1]。空数组也返回[-1, -1]

算法原理

1.暴力解法:直接遍历数组,找到数值第一次出现的位置begin,保存下来,继续寻找,找到最后一个出现的位置end,再保存,得到区间[begin, end],O(N)

2.二分查找

如果用朴素二分,会导致,最后的时间复杂度变为暴力解法的时间复杂度

1.查找区间的左端点

注意:当mid所处位置的值>=target,right 不能再等于mid的左边值了,而是直接 == mid,因为有可能mid所处的位置就是target

难点:细节处理

1.循环条件

left <= right 错误

left < right

1.left == right的时候,就是最终结果,无需判断

2.如果判断,就会死循环

三种情况讨论:

  • 有结果,right一定不会越过ret ,当left == right 时,就是最终的结果,无序判断

  • 全大于target

只会命中第二个条件,left不会动,right一直向右边移动,直到left和right相遇为止就结束,最后判断left处的值是否等于target。不等于 返回 -1

  • 全小于target,只会命中第一个条件,right不会动,left一直向右边移动,直到left和right相遇为止就结束。不能判断,判断就会死循环。

 2.求中点的操作,如何求mid

left + (right - left)/2

left + (right - left + 1)/2 --->  会陷入死循环

2.查找区间的右端点

类似查找左端点

1.循环条件 -> 同查找左端点: left < right 

2.求中点的方式:

left + (right - left) / 2

left + (right - left + 1) / 2

注意此时只剩两个元素,left指向mid,如果命中第一个条件,left就一直指向mid,

使用第一种方式: left + (right - left) / 2 = left 就会导致陷入死循环.

选择第二种方式 :mid = left + (right - left + 1) / 2 = left + 1 ,left = mid =  left + 1,就会和right指向同一个元素,就能终止循环

代码如下:

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {if(nums.size() == 0) return {-1, -1};int begin = 0, end = 0;// 1.查找二分左端点int left = 0, right = nums.size() - 1;while (left < right) {int mid = left + (right - left) / 2;if (nums[mid] < target) {left = mid + 1;} else {right = mid;}}if (nums[left] != target)return {-1, -1};elsebegin = left;// 2.二分右端点left = 0, right = nums.size() - 1;while (left < right) {int mid = left + (right - left + 1) / 2;if (nums[mid] <= target) {left = mid;} else {right = mid - 1;}}end = right;return {begin, end};}
};

 

总结二分模版(查找区间左端点的模版,和查找区间右端点的模版)

左端点

 while (left < right) {
int mid = left + (right - left) / 2;
if (......) {
left = mid + 1; //记这里
} else {
right = mid;
}
}

右端点

 while (left < right) {
int mid = left + (right - left + 1) / 2;
if (......) {
left = mid;
} else {
right = mid - 1;  //记这里
}
}

如何记忆:当下面出现 - 1的时候上面就 +1

三、力扣69——x的平方根

题目解析

返回一个比真实值小的最近整数。

算法原理

1.暴力解法:

依次从1开始算他们的平方数,如果没有刚好等于的,就返回刚大于x的前一个数的原始值,也就是找到这个刚大于x的数后-1

2.二分查找:

具有二段性,不朴素的二分查找:

准备left ,right,  mid:left从1开始,right就为x(因为可能x = 1):

1.mid * mid <= x,left = mid

2.mid * mid > x   ,  right = mid - 1

代码编写 

注意long long防止溢出

class Solution {
public:int mySqrt(int x) {if (x < 1)return 0; // 处理边界情况long long left = 1, right = x;while (left < right) {long long mid = left + (right - left + 1) / 2;if (mid * mid <= x) {left = mid;} else {right = mid - 1;}}return left;}
};

四、力扣35——搜索插入位置

算法原理

二分查找:

1.恰好能找到这个值

2.找插入位置

        在中间:左边是小于 t ,右边都是大于 t 的

        在边缘:全部大于t ,直接返回 0 ,全部小于t,直接返回nums.end()

所要找的值,如果是要刚好等于,返回mid,插入的位置一定是在刚好大于target的这个位置,因此我们需要分的是小于和大于等于这二段

理解:

如果我们的nums[mid]值小于target,那么我们需要找的下标一定不会再这个区间内,因此是left 

= left + 1 ,如果nums[mid]值大于等于target,那么需要找的值就一定在这个区间内right = mid

代码如下:

class Solution {
public:int searchInsert(vector<int>& nums, int target) {int left = 0,right = nums.size() - 1;int max = nums[right];while(left < right){int mid = left +(right - left) / 2;if(nums[mid] < target) left = mid + 1;else right = mid;}if(nums[left] < target) return left + 1;else return left;}
};

五、力扣852——山脉数组的峰顶索引 

算法原理

二分查找:

当arr[mid] > arr[mid - 1]的时候,说明这是上坡,不一定到转折点

arr[mid] < arr[mid - 1]的时候,说明这是下坡,前面一定有一个转折点也就是山坡

这就是这道题的二义性

代码如下:

class Solution {
public:int peakIndexInMountainArray(vector<int>& arr) {int left = 0,right = arr.size() - 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;}
};

六、力扣162——寻找峰值

题目解析

遇上题不同的是,有多个峰值,最左和最右都是比所有峰值小的

算法原理

1.暴力解法:

从第一个位置开始,一直向后走,分情况讨论,O(N),这道题的数据范围比较小,直接暴力解法也是可以的,峰值有可能在最后

 2.二分查找:优化暴力解法

这里还是类似于上一道题 这道题特殊在

 

 

上图即为我们会遇到的山峰

  • 1是遇到递减,说明之前一定有个山峰。 
  • 2是递增在递减,中间一定有个山峰。
  • 3是一直递增,但因为nums[n] = 负无穷,所以在后面一定会的碰到一个递减,在此之间有一个山峰。

代码如下:

class Solution {
public:int findPeakElement(vector<int>& nums) {int left = 0,right = nums.size() - 1;while(left < right){int mid = left +(right - left) / 2;if(nums[mid] < nums[mid + 1]) left = mid + 1;else right = mid;}return left;}
};

七、力扣153寻找旋转排序数组中的最小值

如何旋转:

旋转n次后,nums原来就是一个升序排列的数组,传给我们的是经过n次旋转的,我们需要得到原始的升序排列的数组 ,并且返回数组中最小的元素,注意数组元素互不相同。

算法原理

1.暴力查找最小值:从前往后遍历,找最小值。慢在,没有使用这个数组的特性

2.二分查找:

这个数组一定存在两段数值上升的递增区间。

二段性:以D为参照点,会发现,左边的AB段的数组一定都是大于D的值的,而CD这个区域的所有值都是小于等于D的,而我们最终要找的值,就是C点。

如果mid落在A~B这个区间:nums[mid] < nums[n - 1](C ~ D)

如果mid落在C~D这个区间:nums[mid] >= nums[n - 1](C ~ D)

 代码如下:

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

八、力扣LCR 173——点名

题目解析

就是一共有9个人,但是有一个人的学号不在,因此数组长度只会有8个,就要找到这个不在的人

算法原理

  1. 解法一:高斯求和公式:原值求和 - 现有值的和 = 缺失的数 
  2. 解法二:哈希表 ,建立一个 n+1大小的哈希表,遍历原始数组往里填,最后查看哈希表哪一个位置没有填,就是缺失的数字
  3. 解法三:直接遍历,for循环找,不等于则返回这个值
  4. 解法四:位运算,将题目所给数组,和一个完整数组的元素进行位运算异或。最后异或的结果就是位运算的结果。

前四种的时间复杂度都是O(N);

解法五:二分查找:

发现二段性:
从缺失值处开始划分,左边的值和下标都是一一对应的,当遇到不对应的值的时候就返回下标

细节问题:如果数组是【0,1,2,3】,缺失的值是4,但是在我们的二分查找里面并没有得到这个值

因此在最后还需要判断一下records[left] == records[right] == records.size() - 1,的话,就返回left + 1

代码编写

class Solution {
public:int takeAttendance(vector<int>& records) {//二分int left = 0,right = records.size() - 1;int cnt = records[right];while(left < right){int mid = left + (right - left + 1) / 2;if(records[mid] == mid) left = mid;else right = mid - 1;}if(records[left] == left) return left + 1;else  return left;}
};

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

http://www.xdnf.cn/news/15548.html

相关文章:

  • 玩转Docker | 使用Docker部署vnStat网络流量监控服务
  • Python编程基础(六)| 用户输入和while循环
  • 算法精讲--正则表达式(二):分组、引用与高级匹配技术
  • GENERALIST REWARD MODELS: FOUND INSIDE LARGELANGUAGE MODELS
  • 7.17 滑动窗口 |assign |memo |pii bfs
  • 【Linux】如何使用nano创建并编辑一个文件
  • 使用token调用Spring OAuth2 Resource Server接口错误 insufficient_scope
  • Redis1:高并发与微服务中的键值存储利器
  • 第四章 OB SQL调优
  • OJ题目里面的复杂图形的输出类型的汇总展示(巧妙地利用对称性offset偏移量)
  • 轻松将文件从 iPhone 传输到 Mac
  • 牛客:HJ26 字符串排序[华为机考][map]
  • 暑期算法训练.2
  • ArcGISPro应用指南:使用ArcGIS Pro创建与优化H3六边形网格
  • PHP 社区正在讨论变更许可证,预计 PHP 9.0 版本将完全生效
  • 基于MATLAB的决策树DT的数据分类预测方法应用
  • 【Unity】Mono相关理论知识学习
  • SQL中对字符串字段模糊查询(LIKE)的索引命中情况
  • 第3章 Excel表格格式设置技巧
  • Win11专业工作站版安装配置要求
  • [NOIP][C++] 树的重心
  • Word 文档合并利器:基于 org.docx4j 的 Java 实现全解析
  • Java线程创建与运行全解析
  • GraphQL与REST在微服务接口设计中的对比分析与实践
  • Windows 启动后桌面黑屏,其他程序正常运行
  • Java接口:小白如何初步认识Java接口?
  • 一点点dd
  • WPF 加载和显示 GIF 图片的完整指南
  • 聚焦AI与物流核心技术:2025智慧物流论坛及长三角快递物流展会9月上海开幕
  • API Gateway HTTP API 控制客户端访问 IP 源