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

二分查找算法(一)

一、力扣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.dtcms.com/a/282801.html

相关文章:

  • rocketmq两主两从搭建
  • 【Jupyter】个人开发常见命令
  • 多房间 WebSocket 连接管理设计:从单例模式到多终端连接池
  • 【Unity】IL2CPP相关理论知识学习
  • Transformer 架构
  • 多语言json文件内\n换行不生效问题
  • ScratchCard刮刮卡交互元素的实现
  • 列车调度(vector)
  • 前端vue3获取excel二进制流在页面展示
  • 【unity知识点】已知发射的初始位置和目标位置,计算发射初速度,实现投掷物品或者弓箭手做抛体线运动精准的击中目标
  • C语言 --- 函数递归
  • Python编程基础(六)| 用户输入和while循环
  • 康华生物:以创新疫苗书写国产突围之路​​​
  • 李宏毅2025《机器学习》第七讲-推理模型:从原理、流派到未来挑战
  • 2025年自动化工程、物联网与计算机应用国际会议(AEITCA 2025)
  • 【时序数据库-iotdb】时序数据库iotdb的可视化客户端安装部署--dbeaver
  • 基于Spring AI Alibaba的智能知识助手系统:从零到一的RAG实战开发
  • 最细,Web自动化测试入门到精通整理,一套打通自动化测试...
  • ASP .NET Core 8集成Swagger全攻略
  • 从零开发足球比分APP:REST API与WebSocket的完美搭配
  • HAProxy简介及配置文件详解
  • ESP‑IDF 默认的连接流程是
  • 2_概要设计编写提示词_AI编程专用简化版
  • 快速开发汽车充电桩的屏幕驱动与语音提示方案
  • __is_constexpr(x)宏介绍---max()宏扩展
  • Linux 常用指令
  • 信而泰×DeepSeek:AI推理引擎驱动网络智能诊断迈向 “自愈”时代
  • Java基础语法补充v2
  • C# --- 单例类错误初始化 + 没有释放资源导致线程泄漏
  • The 2024 ICPC Asia Shenyang Regional Contest B. Magical Palette