二分算法可以适用于非有序数组,只要找到二段性,也可以用二分算法优化。
1. 山脉数组的峰顶索引
解题思路:
- 暴力枚举
O(N):逐个枚举数组中的元素,直至找到峰值。 - 二分查找
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. 寻找峰值
解题思路:
- 暴力枚举
O(N):逐个枚举数组中的元素,直至找到峰值所在位置。 - 二分查找
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. 搜索旋转排序数组中的最⼩值
解题思路:
- 暴力枚举
O(N):逐个枚举数组中的元素,直至找到最小值。 - 二分查找
O(NlogN):本题中,根据数组本身形成的二段性 特点,我们可以选取首 / 尾参元素作为参照值key,用于比较大小 / 缩短区间,这里我们选取数组最后一个元素 作为key,则可以分为以下两种情况:

Q:为什么不选取首元素作为key?
A:因为选取key后,left和right区间范围中就不包含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];
}
};
// 本期内容就到这里啦,如果对你有帮助,请三连支持!我是青云,我们下期见^_~