[LC优选算法#8] 二分 | 山脉数组的峰顶索引 | 寻找峰值 | 搜索旋转排序数组中的最⼩值

二分算法可以适用于非有序数组,只要找到二段性,也可以用二分算法优化。

1. 山脉数组的峰顶索引

山脉数组的峰顶索引

解题思路:

  1. 暴力枚举 O(N):逐个枚举数组中的元素,直至找到峰值。
  2. 二分查找 O(logN):虽然数组中的元素不是有序的,但根据数组中具有峰值的特点,我们可以把数组分为两段 :前半段元素递增,后半段元素递减。因此可以使用二分算法优化暴力算法,分类讨论如下:
cpp 复制代码
class Solution {
public:
    int peakIndexInMountainArray(vector<int>& arr)
    {
        int left = 0;
        int 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;
    }
};

2. 寻找峰值

寻找峰值

解题思路:

  1. 暴力枚举 O(N):逐个枚举数组中的元素,直至找到峰值所在位置。
  2. 二分查找 O(logN):如果把数组一分为二,那么mid处元素的情况可以分为两种:比后一个元素大 / 比后一个元素小。由于题目要求返回任意一个峰值,那么我们就可以根据这个限制条件逐渐缩减区间,因为峰值总会落在较大元素的一侧

    这样数组就具有了二段性,可以使用二分查找的方式优化暴力算法:
cpp 复制代码
class Solution {
public:
    int findPeakElement(vector<int>& nums)
    {
        int n = nums.size();
        int left = 0;
        int right = n - 1;
        
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] >= nums[mid + 1])
            {
                right = mid;
            }
            else
            {
                left = mid + 1;
            }
        }

        return left;
    }
};

3. 搜索旋转排序数组中的最⼩值

搜索旋转排序数组中的最⼩值

解题思路:

  1. 暴力枚举 O(N):逐个枚举数组中的元素,直至找到最小值。
  2. 二分查找 O(NlogN):本题中,根据数组本身形成的二段性 特点,我们可以选取首 / 尾参元素作为参照值key,用于比较大小 / 缩短区间,这里我们选取数组最后一个元素 作为key,则可以分为以下两种情况:

    Q:为什么不选取首元素作为key?
    A:因为选取key后,leftright区间范围中就不包含key了,对于以上非递增区间,选取首 / 尾元素没有影响;但对于递增区间这种特殊情况,如果选取首元素就会跳过 数组最小值:

    因此解决方法是,如果需要选取首元素作为key,需要额外讨论递增这种特殊情况,否则就会漏解。
cpp 复制代码
//选取末尾元素作为target
class Solution {
public:
    int findMin(vector<int>& nums)
    {
        int n = nums.size();
        int left = 0;
        int right = n - 1;
        int target = nums[n - 1];
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] > target)
            {
                left = mid + 1;
            }
            else
            {
                right = mid;
            }
        }

        return nums[left];
    }
};
cpp 复制代码
//选取首元素作为target
class Solution {
public:
    int findMin(vector<int>& nums)
    {
        int n = nums.size();
        if(nums[0] < nums[n - 1]) return nums[0]; //递增

        int left = 0;
        int right = n - 1;
        int target = nums[0];
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] < target)
            {
                right = mid;
            }
            else
            {
                left = mid + 1;
            }
        }

        return nums[left];
    }
};

// 本期内容就到这里啦,如果对你有帮助,请三连支持!我是青云,我们下期见^_~