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

本章节持续更新。

相关推荐
蒸蒸yyyyzwd几秒前
秋招学习笔记 Day 64 之准备系统软件开发工程师岗位笔记
c++·八股
奇树谦2 分钟前
LRU 缓存原理与 C++ 实践示例
开发语言·c++·缓存
toooooop84 分钟前
FastAdmin Hook 机制与插件错误透传:`$result` 之外的信封
开发语言·fastadmin
小马过河R28 分钟前
EdgeOne深度解析:从原理、适用场景到COS接入的技术逻辑
java·开发语言·前端·spring·cos
多弗朗皮卡丘36 分钟前
C++ 红黑树
开发语言·c++·红黑树
ZoeLandia1 小时前
TypeScript 04:类型断言与类型守卫
开发语言·前端·typescript
geovindu1 小时前
rust: pool Pattern
开发语言·后端·设计模式·rust·对象池简写模式
曼巴UE51 小时前
UE 属性同步
网络·c++·ue5
无忧.芙桃1 小时前
图的精髓:邻接矩阵与遍历全解析
c++·算法·面试·深度优先·广度优先
jimy11 小时前
标准的动态类型定义与“多态”无关,非多态对象同样会出现动态类型不同于静态类型的情形
开发语言·c++