二分查找进阶:巧用“二段性”寻找极值

相关文章:

C++算法入门:二分查找 I (二分查找|在排序数组中查找元素的第一个和最后一个位置|X的平方根|搜索插入位置)

力扣困难算法精解:串联所有单词的子串与最小覆盖子串


目录

1.山脉数组的峰顶索引

理解题意

算法原理

2.寻找峰值

理解题意

算法原理

3.寻找旋转排序数组中的最小值

理解题意

算法原理

思考:如果固定nums最左边的数,要怎么做?


1.山脉数组的峰顶索引

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

理解题意

题目给定一个数组,这个数组的元素呈现先严格递增,后严格递减的趋势,形状像一座"山脉"。我们需要找到这个山脉的"峰顶"元素的索引。题目保证输入的数组一定是一个山脉数组,且长度至少为 3。

算法原理

算法原理因为数组具有非常明显的"二段性"(左侧单调递增,右侧单调递减),我们可以利用二分查找来寻找分界点(峰顶)。在这段代码中,我们通过比较 arr[mid] 和 arr[mid - 1] 的大小关系来确定当前 mid 所处的坡度:

1.计算 mid 时使用了向上取整 mid = left +(right- left + 1)/ 2 。

2.如果 arr[mid] < arr[mid - 1] ,说明 mid 和 mid - 1 处于下降区间(右侧坡度),峰顶一定在 mid-1 或更左侧,所以区间缩小为 [left,mid - 1] ,即 right = mid - 1 。

3.否则(arr[mid] > arr[mid -1] ),说明处于上升区间(左侧坡度) 或者mid 刚好是峰顶,峰顶一定在 mid 或更右侧,所以区间缩小为 [mid,right],即 left = mid 。

  1. 循环直到 left == right ,找到峰顶。

    class Solution
    {
    public:
    int peakIndexInMountainArray(vector<int>& arr)
    {
    int left = 1, right = arr.size() - 2; // 细节:第一个和最后一个可以省去
    while(left < right)
    {
    int mid = left + (right - left + 1) / 2;
    if(arr[mid] < arr[mid - 1])
    {
    right = mid - 1;
    }
    else
    {
    left = mid;
    }
    }
    return left;
    }
    };

2.寻找峰值

https://leetcode.cn/problems/find-peak-element/

理解题意

给定一个整数数组nums,找到其中的峰值元素(严格大于左右相邻值的元素)并返回其索引。数组中可能存在多个峰值,返回其中任意一个即可。题目假设数组越界位置(nums[-1] 和nums[n])的值为负无穷小。

算法原理

虽然整个数组不具备单调性,但我们可以利用局部的单调性来使用二分查找。只要顺着递增的方向走,由于边界是负无穷,最后一定能走到一个峰值。

  1. 计算中间位置mid,比较 nums[mid] 和 nums[mid + 1] 。

  2. 如果 nums [mid] < nums[mid + 1] ,说明此时处于一个向上的局部坡度。因为最右侧边界是负无穷,继续往右走必定能遇到峰值,所以向右收缩区间:left = mid + 1 。

3.如果nums[mid] > nums[mid + 1](题目保证相邻元素不相等),说明此时处于向下的局部坡度,或者 mid 本身就是局部峰值。在 mid 及其左侧必定存在峰值,所以向左收缩区间:right = mid 。

4.循环结束时 left == right,即可找到一个峰值。

复制代码
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;
    }
};

3.寻找旋转排序数组中的最小值

https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/description/

理解题意

一个原本严格升序排列的数组,在某个未知的点进行了旋转(比如[0,1,2,4,5,6,7] 变成了

4,5,6,7,0,1,2\] )。 要求在 O(log n) 的时间复杂度内找到数组中的最小值。 数组中不存在重复元素。 ### 算法原理 固定nums 最右边的数,利用旋转数组的二段性:旋转后的数组可以看作由两段升序子数组组成(我们称左边部分为第一段,右边部分为第二段)。最小值显然是第二段的第一个元素。我们取最右侧元素nums\[right\] 作为基准进行比较: 1.因为第二段的所有元素都小于等于×,而第一段的所有元素都严格大于×。 2.当nums\[mid\] \> x时,说明 mid 落在了第一段,最小值肯定在 mid 的右边,因此 left= mid + 1 3.当 nums\[mid]\<= x 时,说明 mid 落在了第二段,mid 可能是最小值,也可能最小值在它的左侧,因此 right = mid 。 class Solution { public: int findMin(vector& 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]; } }; #### 思考:如果固定nums最左边的数,要怎么做? 如果想以最左边的数作为基准,会遇到一个**特例:数组完全没有旋转(即原本有序的情况)** 。 如果是完全有序的数组,所有数字都大于等于最左边的数,导致无法通过基准值区分"两段"。因此,如果固定最左边的数,必须**先做一次特判**。 具体做法如下: 1.特判:如果 nums\[left\]〈= nums\[right],说明区间\[left,right] 是完全有序的,直接返回nums\[left\] 即可。 2.基准判断:如果没有提前返回,说明数组在该区间内发生了旋转。记录基准值 ×= Ωums\[left]。第一段所有元素均\>=×,第二段所有元素均\< ×。 3.二分查找: 若 nums\[mid\] \>= ×,说明 mid 落在第一段,最小值在右侧,left = mid + 1 。 若nums\[mid\]〈×,说明 mid 落在第二段,最小值在左侧或正是mid,right = mid。(注意:实践中以最右端点作基准最简洁,不需要额外处理未旋转的边界情况,是该题的最优模板。) 本章完。

相关推荐
Mikowoo0074 小时前
CPU_多线程操作图片_代码详解
算法
0 0 04 小时前
CCF-CSP 38-2 机器人复健指南(jump)【C++】考点:BFS/DFS
开发语言·c++·算法·深度优先·宽度优先
小O的算法实验室4 小时前
2025年IEEE TSMCS SCI1区TOP,面向异构多点动态聚合的多阶段粒子群算法,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
xiaoye-duck4 小时前
《算法题讲解指南:优选算法-前缀和》--29.和为k的子数组,30.和可被k整除的子数组
c++·算法
Z9fish4 小时前
sse 哈工大 C 语言编程练习 39
c语言·数据结构·算法
丶小鱼丶4 小时前
数据结构和算法之【二分查找】
java·数据结构·算法
忡黑梨4 小时前
BUUCTF_reverse_[MRCTF2020]Transform
c语言·开发语言·数据结构·python·算法·网络安全
枳颜4 小时前
LeetCode 466:统计重复个数
数据结构·算法·字符串
TYFHVB124 小时前
2026六大主流CRM横评,五大核心维度深度解析
大数据·前端·数据结构·人工智能
爱和冰阔落4 小时前
【C++STL上】栈和队列模拟实现 容器适配器 力扣经典算法秘籍
数据结构·c++·算法·leetcode·广度优先