算法入门:专题二分查找算法 模板总结 题目练手 :排序数组中查找元素的第一个和最后一个位置 第一个错误的版本 查找x的平方根 搜索插入位置 山脉数组的封顶索引

🎬 胖咕噜的稞达鸭个人主页
🔥 个人专栏 : 《数据结构《C++初阶高阶》
《Linux系统学习》
《算法入门》

⛺️技术的杠杆,撬动整个世界!


二分查找算法

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

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果 target 存在返回下标,否则返回 -1

你必须编写一个具有 O(log n) 时间复杂度的算法。

解法一:暴力解法->O(N)

从数组下标索引为0访问到数组末尾下标;

解法二:二分查找算法,当数组有二段性的时候就可以二分查找,先排序,拿取到

cpp 复制代码
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left=0,right=nums.size()-1;
        while(left<=right)
        {
            //int mid=(left+right)//不建议这样写,会有溢出风险
            int mid =(right-left)/2+left;
            if(nums[mid]>target)right=mid-1;
            else if(nums[mid]<target)left=mid+1;
            else return mid;
        }
        return -1;
    }
};

总结朴素二分模板:

go 复制代码
while(left <= right)//(用于循环结束条件)
{
	int mid =left+(right-left)/2;//(求中间点,防止溢出风险)
	if(...)
	{
		left = mid+1;
	}
	else if(...)
	{
		right = mid-1;
	}
	else 
		return ...
}

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

\[[https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/](%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%85%83%E7%B4%A0%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%92%8C%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AA%E4%BD%8D%E7%BD%AE)\]

题目解析

非递减顺序排列的整数数组nums,和一个目标值target,给定目标值在数组中的开始位置和结束位置。

举例:nums = [ 5,7, 7, 8 ,8, 10],target = 8;返回位置【3, 4】

算法原理

解法一:暴力枚举,遍历一遍数组,找到target值,在这个位置标记为begin,向后遍历,找到最后一个符合target ,标记为end,然后返回[begin,end]这段区间的起始位置,这样果不其然就会超时。

解法二:朴素二分查找

定义left在数组索引为0的位置,right在数组最后一个位置。

查找区间左端点:

先查找中间点的位置,mid,如果

  1. mid < target ,left = mid + 1;
  2. mid >= target,right = mid (不同于朴素二分查找,right不能小于mid)

那么怎么判断循环条件(left < right):
问题:为什么不能加等号?

这里分三种不用进循环的情况:

  1. 有结果:区间内left=right的时候,就是最终结果,无需判断;
  2. 区间内部的值都是大于target,right一定会向左移动,一直移动到left的位置,判断以下,left是否等于target;
  3. 区间内部的值都是小于target,left向右移动,一直移动到right的位置,判断以下,right是否等于target;

如果判断了就会进入死循环。
求中点的操作:

  1. left+(right-left)/2;
  2. left+(right-left+1)/2;
    1得到的中点靠左边的位置,2得到的中点靠右边的位置,如果判断到最后,也就是left在right相邻位置,
    1会得到mid刚好在left的位置,此时left移动,left=mid+1,也就是落在了right的位置,right=left,此时结束循环的条件;
    2会得到mid在right的位置,此时right=mid,并且left<right没有结束循环,
    left+(right-left+1)/2,mid再次落到right 的位置,left<right,还是满足循环的条件,mid不停地落在right的位置,一直满足循环的条件,但是mid位置不发生任何变化,结果就是陷入死循环。
查找区间右端点:
  1. 如果mid<=target,移动left,left=mid,接着在【left,right】的位置中查找;
  2. 如果mid >target,移动right,right=mid-1,接着在【left,right】的区间中查找。

处理细节:
循环条件 :必须是left<right;一旦相等就会死循环。
求中点的方式:

  1. left+(right-left)/2;
  2. left+(right-left+1)/2;
    只能用left+(right-left+1)/2,理由:
    如果判断到最后,left跟right相邻的位置,
    1求到的midleft的位置,left=mid,此时left<right,满足循环的条件,不会结束循环,再次求mid,left=mid,依旧满足循环的条件,mid的位置不发生任何变化,结果就是会陷入死循环;
    2求到的mid是right的位置,此时right=mid-1,刚好落在了left的位置,right=left,结束循环。
cpp 复制代码
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        //处理边界情况
        if(nums.size() == 0)return {-1,-1};
        int begin = 0;
        int end=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;
        }
        //判断结果
        if(nums[right]!=target)return {-1,-1};
        else end = right;
  
        //最后返回最终结果
        return {begin,end};
    }
};
总结二分模板:
1. 查找左端点的模板:
cpp 复制代码
while(left<right)
{
	int mid = left+(right-left)/2;
	if(......)left=mid+1;
	else right=mid;
}
2. 查找右端点的模板:
cpp 复制代码
while(left<right)
{
	int mid = left+(right-left+1)/2;
	if(......)left=mid;
	else right=mid-1;
}

如果硬要选择死记硬背(阴险阴险嘿嘿嘿)可以找规律,当下面出现-1的时候,上面就+1。(右端点)

第一个错误的版本

https://leetcode.cn/problems/first-bad-version/description/

题目解析:

本质上就是查找左端点的问题,左端点到整个数组末尾都是不符合要求的错误版本,由于我们要查找的是

第一个错误版本,所以返回的是左端点的下标。

这里有一个小的细节,在定义左端点left,和右断点right,left不可以从索引为0的位置开始,根据现实情况,如果第一个版本的产品不合格,就一定不会在不合格产品的基础上开发更高版本的产品。由于要计算中间点,所以相应的right=n.

而且如果mid是错误版本,就说明第一个错误版本一定在[1,mid-1]中,right=mid-1;

如果mid不是错误版本,就说明第一个错误版本在[mid+1,n]中,left= mid+1;

cpp 复制代码
class Solution {
public:
    int firstBadVersion(int n) {
        int left=1,right=n;
        while(left<=right)
        {
            int mid =left+(right-left)/2;
            //如果中间值mid是错误版本,说明【1,mid】中有第一个错误版本
            if(isBadVersion(mid))right = mid - 1;
            else left = mid+1;//如果中间值mid不是错误版本,说明[mid+1,n]中会有第一个错误版本
        }
        return left;
    }
};

查找x的平方根

https://leetcode.cn/problems/sqrtx/description/

cpp 复制代码
class Solution {
public:
    int mySqrt(int x) {
        if(x == 0)return 0;//题目要求是非负整数(正整数和0),如果是0,就直接返回0
        int left=1,right=x;
        while(left<right)//如果left=right=1,就不用判断了
        {
            long long mid = left + (right-left+1)/2;//防止溢出,用long long
            if(mid * mid <= x)left=mid;
            else right = mid-1;
        }
        return left;
    }
};

搜索插入位置

https://leetcode.cn/problems/search-insert-position/

解法一:暴力解法

解法二:二分查找

cpp 复制代码
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int left=0,right=nums.size()-1;
        if(target>nums[right])return right+1;//如果要插入的值比整个数组中的值都大,按照元素顺序返回right+1
        while(left<right)
        {
            int mid = left+(right-left)/2;
            if(nums[mid]<target)left=mid+1;
            else right = mid;
        }
        return left;
    }
};

山脉数组的封顶索引

https://leetcode.cn/problems/peak-index-in-a-mountain-array/description/

cpp 复制代码
class Solution {
public:
    int peakIndexInMountainArray(vector<int>& arr) {
        int left=1,right=arr.size();
        while(left<right)
        {
            int mid=left+(right-left+1)/2;
            if(arr[mid]>arr[mid-1])left=mid;
            else right=mid-1;
        }
        return left;
    }
};
相关推荐
松涛和鸣1 小时前
21、单向链表完整实现与核心技巧总结
linux·c语言·数据结构·算法·链表
未来之窗软件服务1 小时前
幽冥大陆(三十六)S18酒店门锁SDK rust语言——东方仙盟筑基期
开发语言·c++·rust·智能门锁·东方仙盟sdk·东方仙盟一体化
TL滕1 小时前
从0开始学算法——第三天(数据结构的操作)
数据结构·笔记·学习·算法
Aaron15881 小时前
基于FPGA实现卷积方法比较分析
arm开发·算法·fpga开发·硬件架构·硬件工程·射频工程·基带工程
Emilia486.1 小时前
C++ 类与对象:解锁面向对象编程的核心密码(上)
开发语言·c++
成豆o((⊙﹏⊙))o.1 小时前
C语言基础知识,仅供自己参考
c语言·开发语言
Mr_WangAndy1 小时前
C++17 新特性_第二章 C++17标准库特性_std::invoke和std::invoke_result_t
c++·invoke·c++40周年·c++17新特性·invoke_result
AndrewHZ1 小时前
【图像处理基石】什么是分水岭算法?
图像处理·算法·计算机视觉·图像分割·cv·形态学分割
前端小L1 小时前
回溯算法专题(五):去重与剪枝的双重奏——攻克「组合总和 II」
算法·剪枝