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;
}

本章节持续更新。

相关推荐
Boilermaker199211 小时前
[Java 并发编程] Synchronized 锁升级
java·开发语言
MM_MS12 小时前
Halcon变量控制类型、数据类型转换、字符串格式化、元组操作
开发语言·人工智能·深度学习·算法·目标检测·计算机视觉·视觉检测
꧁Q༒ོγ꧂12 小时前
LaTeX 语法入门指南
开发语言·latex
njsgcs12 小时前
ue python二次开发启动教程+ 导入fbx到指定文件夹
开发语言·python·unreal engine·ue
alonewolf_9912 小时前
JDK17新特性全面解析:从语法革新到模块化革命
java·开发语言·jvm·jdk
古城小栈12 小时前
Rust 迭代器产出的引用层数——分水岭
开发语言·rust
ghie909013 小时前
基于MATLAB的TLBO算法优化实现与改进
开发语言·算法·matlab
恋爱绝缘体113 小时前
2020重学C++重构你的C++知识体系
java·开发语言·c++·算法·junit
wuk99813 小时前
VSC优化算法MATLAB实现
开发语言·算法·matlab
AI小怪兽13 小时前
基于YOLOv13的汽车零件分割系统(Python源码+数据集+Pyside6界面)
开发语言·python·yolo·无人机