二分查找算法:解题(二分查找,山脉数组的峰顶索引,在排序数组中查找元素的第一个和最后一个位置) [C++]

在这里插入图片描述

欢迎来到我的:世界

希望作者的文章对你有所帮助,有不足的地方还请指正,大家一起学习交流 !


前言

二分查找算法,也称为折半查找算法,是一种在有序数组中查找特定元素的高效搜索算法。

算法原理:二分查找算法的核心原理是利用数组的有序性,每次将查找范围缩小一半。
它首先取数组中间的元素与目标元素进行比较,如果中间元素恰好是目标元素,则查找成功;
如果目标元素小于中间元素,那么目标元素只可能存在于数组的前半部分,于是可以忽略后半部分,继续在前半部分中进行查找;
如果目标元素大于中间元素,则目标元素只可能存在于数组的后半部分,同样可以忽略前半部分,继续在后半部分中进行查找。
重复上述过程,直到找到目标元素或者确定目标元素不存在于数组中。


内容

详细介绍:二分查找算法,我可以具体分为两种:1.朴素二分查找算法 2.边界二分查找算法
接下来我将用第一题来介绍朴素二分查找算法
用第二,三题介绍边界二分查找算法

第一题:二分查找

在这里插入图片描述

解题思路:这题比较简单,因为他直给了一个升序导数组,所以应该一瞬间就能想到二分查找算法,至于朴素二分查找,简单介绍:就是利用他最关键的升序导条件,可以知道这数组的单调性,利用这点就解决此题;
步骤:
初始化两个指针,left 指向数组起始位置(索引为 0),right 指向数组末尾位置(索引为 nums.size() - 1)。
进入循环,只要 left <= right,就继续查找:
计算中间位置 mid = left + (right - left) / 2,这样可避免 left + right 导致的整数溢出。
将 nums[mid] 与 target 比较:
若相等,直接返回 mid。
若 nums[mid] > target,说明目标值在左半部分,更新 right = mid - 1。
若 nums[mid] < target,说明目标值在右半部分,更新 left = mid + 1。
循环结束还未找到,则返回 -1 表示目标值不存在。

代码实现:

#include <vector>
using namespace std;

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) {
                return mid;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return -1;
    }
};

这里可以看到,这不就是基本的二分查找,为啥叫朴素二分查找,因为他简单啊,这种类型几乎可以利用这一个模板解决所有的类似的题了,接下来我将介绍边界二分查找算法思路:

第二题 山脉数组的峰顶索引


在这里插入图片描述

解题思路:对于山脉数组,我们可以利用其先增后减的特性进行二分查找。但是不能在利用朴素的二分查找了,这时候我们的边界二分查找就登场了;
先来了解一下这个算法:
在常规二分查找中,是在有序数组中查找一个确切的目标值。而边界二分查找是在有序数组中查找满足某种条件的边界元素,比如查找第一个大于等于目标值的元素位置,或第一个小于等于目标值的元素位置等。它基于二分查找每次将查找范围减半的思想,通过对中间元素的判断和调整查找区间,逐步逼近目标边界。

初始化指针:设置两个指针,left指向数组的起始位置,即left = 0;right指向数组的末尾位置,即right = arr.size() - 1。
二分查找循环:使用一个while循环,只要left < right,就继续查找。因为当left == right时,就找到了峰值元素的下标。
计算中间位置:在每次循环中,计算中间位置mid = left + (right - left) / 2。这样计算可以避免left + right可能导致的整数溢出问题。
比较与调整指针:比较arr[mid]和arr[mid + 1]的大小:
如果arr[mid] < arr[mid + 1],说明从mid到mid + 1这一段数组是递增的,那么峰值元素必然在mid的右侧。因此,更新left = mid + 1,将查找范围缩小到右半部分。
如果arr[mid] > arr[mid + 1],说明从mid到mid + 1这一段数组是递减的,那么峰值元素要么是mid,要么在mid的左侧。所以,更新right = mid,将查找范围缩小到左半部分。
返回结果:当循环结束时,left和right会指向同一个位置,这个位置就是峰值元素的下标,直接返回left即可。

该题的算法流程图:
在这里插入图片描述

代码实现:

class Solution {
public:
    int peakIndexInMountainArray(vector<int>& arr) {
        int left=0,right=arr.size()-1;

        while(left<right)
        {
            int mid=left+(right-left+1)/2;

            if(arr[mid]>=arr[mid-1])left=mid;
            else right=mid-1;
        }

        return left;
    }
};

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

在这里插入图片描述

这题同样用到边界二分查找算法,利用二分查找的特性,查找到两个边界值即可;

先查找左边值

 //找左边界位置
        while(left<right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]>=target)right=mid;
            else if(nums[mid]<target)left=mid+1;
        }
        int begin=0;
        //确认左边界
        if(nums[right]!=target)return {-1,-1};
        else begin=right;

循环条件:while(left < right) 表示只要左指针left小于右指针right,就继续在当前区间内查找。
计算中间位置:int mid = left + (right - left) / 2 计算当前查找区间的中间位置mid,这种写法可以避免left + right可能导致的整数溢出问题。
比较与指针调整:
如果nums[mid] >= target,说明目标值的左边界可能在当前中间位置或者左侧,所以将右指针right更新为mid,缩小查找范围到左半部分。
如果nums[mid] < target,说明目标值在当前中间位置的右侧,将左指针left更新为mid + 1,继续在右半部分查找。
确认左边界:循环结束后,left和right会指向同一个位置。此时判断nums[right]是否等于target,如果不相等,说明数组中不存在目标值,返回{-1, -1};如果相等,则将该位置赋值给begin,作为目标值的左边界。

再查找右边值

//找最右边界
        left=0,right=nums.size()-1;
        while(left<right)
        {
            int mid=left+(right-left+1)/2;
            if(nums[mid]<=target)left=mid;
            else if(nums[mid]>target)right=mid-1;
        }
        //确认右边界
        int end;
        if(nums[left]!=target)return {-1,-1};
        else end=left;

指针初始化:重新将左指针left设为 0,右指针right设为数组的末尾位置nums.size() - 1,开始查找目标值的右边界。
循环与计算:while(left < right) 为循环条件。这里计算中间位置的表达式int mid = left + (right - left + 1) / 2 加 1 是为了在left和right差值为 1 时,确保mid能取到right的值,避免陷入死循环。
比较与指针调整:
如果nums[mid] <= target,说明目标值的右边界可能在当前中间位置或者右侧,将左指针left更新为mid,继续在右半部分查找。
如果nums[mid] > target,说明目标值在当前中间位置的左侧,将右指针right更新为mid - 1,缩小查找范围到左半部分。
确认右边界:循环结束后,判断nums[left]是否等于target,若不相等,返回{-1, -1};若相等,则将该位置赋值给end,作为目标值的右边界。

代码实现:

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int left=0,right=nums.size()-1;
        //特殊判断为字符的情况
        if(nums.size()==0)return {-1,-1};

        //找左边界位置
        while(left<right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]>=target)right=mid;
            else if(nums[mid]<target)left=mid+1;
        }
        int begin=0;
        //确认左边界
        if(nums[right]!=target)return {-1,-1};
        else begin=right;
        //找最右边界
        left=0,right=nums.size()-1;
        while(left<right)
        {
            int mid=left+(right-left+1)/2;
            if(nums[mid]<=target)left=mid;
            else if(nums[mid]>target)right=mid-1;
        }
        //确认右边界
        int end;
        if(nums[left]!=target)return {-1,-1};
        else end=left;
        return {begin,end};
    }
};

总结


到了最后:感谢支持

------------对过程全力以赴,对结果淡然处之

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值