【优选算法】二分算法(在排序数组中查找元素的第一个和最后一个位置,寻找峰值,寻找排序数组中的最小值)

二分算法简介:

提到二分我们可能都会想起二分查找,二分查找要求待查找的数组是有序的,与我们今天讲的二分算法不同,并不是数组元素严格按照有序排列才可以使用二分算法,只要数组中有一个点可以将数组分为两个部分,即存在"二段性",就可以使用二分算法解决。

二分算法的模版就是定义一个left指针和right指针,用mid来保存这段区间的中间下标,而求mid的时有一个小细节,如果我们直接采用(left+right)/2 的话,如果left和right的值很大,求mid时数据就会越界了,所以我们可以这样写:mid = left+ (right-left)/2 ,这样就可以解决越界的问题。

求mid的方式其实有两种

  1. mid = left+ (right-left)/2
  2. mid = left+ (right-left+1)/2

两者的使用在数组大小为奇数时没有任何区别,但是如果数组大小为偶数的话,方式1求出来的mid就是靠左边的,方式2求出来的mid是靠右边的,具体使用哪种要看具体的场景了,用错了极容易造成死循环。

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

题目链接:

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

题目介绍:

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

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • nums 是一个非递减数组
  • -109 <= target <= 109
思路:

首先题目都说明了数组是非递减的数组,并且要求我们实现一个复杂度为 O(log n) 的算法,这样的题目基本上就是要让我们用二分算法了。

如果题目是让我们找第一个出现的元素位置或者最后一个元素出现的位置,这样就很简单,以找第一个出现的位置为例,一个元素要么是小于target,要么是大于等于target的,这样就把数组分成了两部分。

如果元素小于target那说明第一个元素的位置一定在他的右边,那就一定不在当前位置的左边,所以我们可以让 left=mid+1,如果元素大于等于target的话,我们只能确定第一个出现的位置一定不在这个下标的右边,而不能确定当前的下标的元素是不是数组中出现的第一个,所以我们可以让right=mid。通过这样right就会不断的向左压缩,当left = right时查找就结束了,至于是不是目标值我们还需要判断一下。

要注意,这里求mid不能使用方式2,假设此时区间只剩下两个元素【0,2】,而我们的目标值时2,如果采用方式2的话,求出来的mid就是指向2的,这样就会造成死循环了。

int begin = -1, end = -1;
int left = 0, right = nums.size() - 1;
while (left < right) {
    int mid = left + (right - left) / 2;
    if (nums[mid] >= target) {
        right = mid;
    } else {
        left = mid + 1;
    }
}
if (nums[left] == target)
    begin = left;
else
    return {-1, -1};

求最后一个元素出现位置的方式与上述的思路一样,但是此时求mid的方式要用方式2,因为这次是从左向右靠近的

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if (nums.empty())
            return {-1, -1};
        int begin = -1, end = -1;
        int left = 0, right = nums.size() - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] >= target) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        if (nums[left] == target)
            begin = left;
        else
            return {-1, -1};

        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[left] == target)
            end = left;
        else
            return {-1, -1};

        return {begin, end};
    }
};

二、寻找峰值

题目链接:

162. 寻找峰值 - 力扣(LeetCode)

题目介绍:

峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞

你必须实现时间复杂度为 O(log n)的算法来解决此问题。

  • 1 <= nums.length <= 1000
  • -231 <= nums[i] <= 231 - 1
  • 对于所有有效的 i 都有 nums[i] != nums[i + 1]
思路:

寻找⼆段性: 任取⼀个点i ,与下⼀个点 i + 1 ,会有如下两种情况:

  • arr[i] > arr[i + 1] :此时「左侧区域」⼀定会存在山峰(因为最左侧是负无穷),那么我们可以去左侧去寻找结果;
  • arr[i] < arr[i + 1] :此时「右侧区域」⼀定会存在山峰(因为最右侧是负无穷),那么我们可以去右侧去寻找结果。

当我们找到「二段性」的时候,就可以尝试用「二分查找」算法来解决问题。

class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        int left=0,right=nums.size()-1;
        while(left<right)
        {
            int mid=left+(right-left)/2;

            if(nums[mid]<nums[mid+1])
            left=mid+1;
            else
            right=mid;
        }
        return left;
    }
};

三、寻找排序数组中的最小值

题目链接:

153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)

题目介绍:

已知一个长度为 n 的数组,预先按照升序排列,经由 1n旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

  • n == nums.length
  • 1 <= n <= 5000
  • -5000 <= nums[i] <= 5000
  • nums 中的所有整数 互不相同
  • nums 原来是一个升序排序的数组,并进行了 1n 次旋转
思路:

数组经过旋转后元素的大小其实就是这样的,c点就是我们要找的这个值

通过图像我们可以发现, [A,B] 区间内的点都是严格大于 D 点的值的, C 点的值是严格小于 D 点的值的。但是当 [C,D] 区间只有⼀个元素的时候, C 点的值是可能等于 D 点的值的。

因此,初始化左右两个指针 left , right :

然后根据 mid 的落点,我们可以这样划分下⼀次查询的区间:

  • 当 mid 在 [A,B] 区间的时候,也就是 mid 位置的值严格大于 D 点的值,下⼀次查询区间在 [mid + 1,right] 上;
  • 当 mid 在 [C,D] 区间的时候,也就是 mid 位置的值严格小于等于 D 点的值,下次 查询区间在 [left,mid] 上。
  • 当区间长度变成 1 的时候,就是我们要找的结果。
class Solution {
public:
    int findMin(vector<int>& nums) {
        int left=0,right=nums.size()-1;
        int x=nums[right];
        while(left<right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]>x)
            left=mid+1;
            else
            right=mid;
        }
        return nums[left];
    }
};
相关推荐
siy23332 小时前
[c语言日寄]结构体的使用及其拓展
c语言·开发语言·笔记·学习·算法
吴秋霖2 小时前
最新百应abogus纯算还原流程分析
算法·abogus
灶龙3 小时前
浅谈 PID 控制算法
c++·算法
菜还不练就废了3 小时前
蓝桥杯算法日常|c\c++常用竞赛函数总结备用
c++·算法·蓝桥杯
金色旭光3 小时前
目标检测高频评价指标的计算过程
算法·yolo
he101013 小时前
1/20赛后总结
算法·深度优先·启发式算法·广度优先·宽度优先
Kent_J_Truman3 小时前
【回忆迷宫——处理方法+DFS】
算法
paradoxjun4 小时前
落地级分类模型训练框架搭建(1):resnet18/50和mobilenetv2在CIFAR10上测试结果
人工智能·深度学习·算法·计算机视觉·分类
sci_ei1234 小时前
高水平EI会议-第四届机器学习、云计算与智能挖掘国际会议
数据结构·人工智能·算法·机器学习·数据挖掘·机器人·云计算
qystca4 小时前
异或和之和
数据结构·c++·算法·蓝桥杯