C++算法入门:二分查找合集(二分查找|在排序数组中查找元素的第一个和最后一个位置)

前言

二分算法是细节最多,最容易写出死循环的算法,但熟练后也是最简单的

二分查找不止可以运用在数组有序的情况下,在数组无序时,满足"二段性"依旧可以引用

此章开始引入模板,但关于模板一定要理解之后记忆而不是死记硬背

模板:

1.朴素的二分查找

2.查找左边界的二分模板

3.查找右边界的二分模板

目录

前言

1.二分查找

理解题意

算法原理

细节问题

扩展

本题做法

代码

朴素二分模板

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

理解题意

题目示例

算法原理

细节处理

左右端点模板


1.二分查找

https://leetcode.cn/problems/binary-search/

理解题意

对升序有序整型数组给定目标值,存在即返回下标,否则返回-1

算法原理

解法一:暴力解法,O(N)

直接遍历,存在即返回

解法二:二分查找

不论数组是否有序,都可以随意选取一个元素,将其与目标值对比,之后再次进行范围的增加或者减小,其本质是数组有"二段性"

细节问题

1.当left > right时,循环结束

2.时间复杂度:

logN的时间复杂度会有多块呢? 2^32 在O(N)下 4 * 10^9,但在O(logN)下只需要32次

扩展

一般进行二分查找的划分是在中间位置进行划分,但为什么?

这与概率学的数学期望有关,从中间位置开始,其时间复杂度是最好的

本题做法

1.定义left,right,mid

2.求mid尽量不要这样写:mid = (left + right) / 2; 而是mid = left + (right - left) / 2; 或者mid = (left + right + 1) / 2;或者mid = left + (right - left + 1) / 2; 可以防止溢出

代码

暴力解法:

复制代码
class Solution {
public:
    int search(vector<int>& nums, int target) {
      int sub = 0;
      for (auto i : nums) {
        if (target == i) {
          return sub;
        }
        sub++;
      }
      return -1;
    }
};

二分解法:

复制代码
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 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;
    }
};

朴素二分模板

复制代码
while(left <= right)
        {
            int mid = left + (right - left) / 2;
            if(......)
            {
                left = mid + 1;
            }
            else if(......)
            {
                right = mid - 1;
            }
            else
                return ......;
        }

注意:一定要是left <= right

求mid尽量不要这样写:mid = (left + right) / 2; 而是mid = left + (right - left) / 2; 或者mid = left + (right - left + 1) / 2; 可以防止溢出

对于式中是否+1,都不会影响最终判断结果:

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

https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/

理解题意

1.非递减:不动或者递增

2.返回目标值最前和最后的位置下标

题目示例

算法原理

解法一:暴力解法,O(N)

定义begin和end,依次查找整个数组

解法二:朴素二分

当mid直接指向目标值时,要想判断mid指向的是起始值还是结束值又或者中间值,还需要将mid向前和向后遍历查看所在位置,此时效率和暴力查找一样(将上述数组全换为3)

解法三:左右边界查找

依旧根据二段性判断

1.查找区间的左端点

细节处理

1.循环条件应该是left < right,而不是left <= right

第一种情况:有结果

当mid == right == left时,就是最终结果,因此不必判断

第二种情况:全大于t

仅仅只需要判断mid的值即可

第三种情况:全小于t

情况与第二种相似

总结此细节:当left == right时就是最终结果,如果判断,就会进入死循环。

2.求中点操作

利用"二段性"

在朴素二分中,防止溢出有:mid = left + (right - left) / 2; (命中左端点)或者mid = left + (right - left + 1) / 2;(命中右端点) 两种操作。

但在此时只能使用mid = left + (right - left) / 2;

当使用mid = left + (right - left + 1) / 2;时:

如果此刻mid满足第二种形式,right会在原地不动,然后接着判断,从而陷入死循环。

2.查找区间右端点

1.循环条件应该是left < right,而不是left <= right

2.求中点方式

在朴素二分中,防止溢出有:mid = left + (right - left) / 2; (命中左端点)或者mid = left + (right - left + 1) / 2;(命中右端点) 两种操作。

但在此时只能使用mid = left + (right - left + 1) / 2;

复制代码
class Solution 
{
public:
    vector<int> searchRange(vector<int>& nums, int target) 
    {
        // 边界情况
        if(nums.size() == 0)
            return {-1,-1};

        int begin = 0;
        // 二分左端点
        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};
        else begin = left;

        // 二分右端点
        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;
        }
        return {begin,right};
    }
};

左右端点模板

复制代码
// 二分左端点
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;
}

本章节持续更新。

相关推荐
小灰灰搞电子6 分钟前
Rust suppaftp 库详解:基于 FTP 客户端实战指南
开发语言·后端·rust
编码浪子8 分钟前
Rust unsafe 与 FFI 互操作生产级实战:把危险关进笼子的四道闸门
开发语言·后端·rust
估值探索者22 分钟前
【AI+量化实战 #05】财报季的信息洪流:用LLM给业绩预告分类+算事件窗口收益
java·c语言·c++·人工智能·python·分类·数据挖掘
Zixhy2 小时前
手撕Reactor模型实现同步高并发服务器及Muduo网络库深入解析
linux·服务器·网络·数据库·c++
傻啦嘿哟8 小时前
某招聘平台爬虫:爬取招聘岗位数据,分析各城市薪资水平
开发语言·爬虫·python
2501_933670798 小时前
2026秋招量化分析岗技能栈:Python、SQL、统计建模、回测项目怎么准备
开发语言·python·sql
李少兄9 小时前
JavaScript 数据类型完全指南
开发语言·javascript·ecmascript
码匠许师傅9 小时前
【设计模式精讲】29.行为型模式总结与对比(Behavioral Patterns Summary)
c++·设计模式·uml
Seoyoneh10 小时前
Agentic Workflow编排架构:云客服从“被动响应”迈向“主动执行”的技术实现
java·开发语言·架构
啦啦啦啦啦zzzz10 小时前
Logger的组装(c++20)
c++·算法·c++20