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

本章节持续更新。

相关推荐
ss2732 小时前
阻塞队列:ArrayBlockingQueue如何用Lock与Condition实现高效并发控制
开发语言·python
lizz312 小时前
C++操作符重载深度解析
java·c++·算法
CodeCraft Studio2 小时前
Vaadin 25 正式发布:回归标准Java Web,让企业级开发更简单、更高效
java·开发语言·前端·vaadin·java web 框架·纯java前端框架·企业级java ui框架
Shirley~~2 小时前
PPTist 幻灯片工具栏Toolbar部分
开发语言·前端·javascript
|晴 天|2 小时前
Promise 与 async/await 错误处理最佳实践指南
开发语言·前端·javascript
_OP_CHEN2 小时前
【Python基础】(三)Python 语法基础进阶:条件循环 + 实战案例,从入门到精通的核心跳板
开发语言·python·python入门·条件语句·循环语句·python基础语法
苹果电脑的鑫鑫2 小时前
.eslintrc.js这个文件作用
开发语言·javascript·ecmascript
ytttr8732 小时前
matlab进行利用遗传算法对天线阵列进行优化
开发语言·算法·matlab
十五年专注C++开发2 小时前
QTableWidget和QTableView插入数据比较
c++·qt·qtablewidget·qtableview