原视频为左程云的B站教学
1 在有序
数组中查找特定元素
基本思想是通过比较中间元素与目标元素的大小关系,将查找范围缩小一半,直到找到目标元素或查找范围为空为止。
时间复杂度O(logN)
因为比如说数组个数为N=16, 最差的情况要分 4 次 ( [ 8 ∣ 8 ] → [ 4 ∣ 4 ] → [ 2 ∣ 2 ] → [ 1 ∣ 1 ] ) ( [8|8] \to [4|4] \to [2|2] \to [1|1] ) ([8∣8]→[4∣4]→[2∣2]→[1∣1]),而 4 = l o g 2 16 4 = log_216 4=log216。即时间复杂度为 O ( l o g N ) O(logN) O(logN)
/* 注意:题目保证数组不为空,且 n 大于等于 1 ,以下问题默认相同 */
int binarySearch(std::vector<int>& arr, int value)
{
int left = 0;
int right = arr.size() - 1;
// 如果这里是 int right = arr.size() 的话,那么下面有两处地方需要修改,以保证一一对应:
// 1、下面循环的条件则是 while(left < right)
// 2、循环内当 array[middle] > value 的时候,right = middle
while (left <= right)
{
int middle = left + ((right - left) >> 1); // 不用right+left,避免int溢出,且更快
if (array[middle] > value)
right = middle - 1;
else if (array[middle] < value)
left = middle + 1;
else
return middle;
// 可能会有读者认为刚开始时就要判断相等,但毕竟数组中不相等的情况更多
// 如果每次循环都判断一下是否相等,将耗费时间
}
return -1;
}
留意 left + ((right - left) >> 1) 结果等用于(right + left) / 2,但更快,且不会int溢出
2 在一个有序数组中查找>=某个数的最左侧的位置
思路依然是二分法,不同于查找某个值找到目标值就停止二分,找最左/右侧位置问题一定是二分到底
int nearLeftSearch(const std::vector<int>& arr, int target)
{
int left = 0;
int right = arr.size() - 1;
int result = -1;
while (left <= right)
{
int mid = left + ((right - left) >> 1);
if (target <= arr[mid]){ // 目标值小于等于mid,就要往左继续找
result = mid;// 暂时记录下这个位置,因为左边可能全都比目标值小了,就已经找到了
right = mid - 1;
} else{ // target > arr[mid]
left = mid + 1;
}
}
return result;
}
3 在一个有序数组中查找<=某个数最右侧位置
- 如果中间元素大于目标值,说明目标值应该在左半部分,因此我们将搜索范围缩小到左半部分,将 right 更新为 mid - 1。
- 如果中间元素小于或等于目标值,说明目标值应该在右半部分或就是当前位置,因此我们更新 result 为当前中间索引 mid,以便记录找到的最右侧位置,并将搜索范围缩小到右半部分,将 left 更新为 mid + 1。
int nearRightSearch(const std::vector<int>& arr, int target)
{
int left = 0;
int right = arr.size() - 1;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (target < arr[mid]) {
right = mid - 1;
} else { // target >= arr[mid]
result = mid;
left = mid + 1;
}
}
return result;
}
4 局部最小值问题
数组arr无序,任意相邻的两个数不等,求一个局部最小的位置(极小值),要求时间复杂度优于O(N)
无序也能二分,只要目标问题在某一边必有解,另一边无所谓,就能够使用二分
1.先判断数组两个边界
- 如果左边界arr[0] < arr[1],已找到
- 如果有边界arr[n-1] < arr[n-2],已找到
- 如果两个边界都不是局部最小,又因为任意相邻的两个数不等,则左边界局部单调递减,右边界处局部单调递增。所以在数组内,必然有极小值点
2.进行二分,判断mid与相邻位置的关系,分为3种情况: (提醒:数组中相邻两个元素是不相等的!)
3.重复过程2直到找到极小值
int LocalMinimumSearch(const std::vector<int>& arr)
{
int n = arr.size();
// 先判断元素个数为0,1的情况,如果题目给出最少元素个>1数则不需要判断
if (n == 0) return -1;
if (n == 1) return 0; // 只有一个元素,则是局部最小值
if (arr[0] < arr[1]) return 0;
int left = 0;
int right = n - 1;
// 再次提醒,数组中相邻两个元素是不相等的!
while (left < right)
{
int mid = left + ((right - left) >> 1);
if (arr[mid] < arr[mid - 1] && arr[mid] < arr[mid + 1]) {
return mid; // 找到局部最小值的位置
} else if (arr[mid - 1] < arr[mid]) {
right = mid - 1; // 局部最小值可能在左侧
} else {
left = mid + 1; // 局部最小值可能在右侧
}
}
// 数组中只有一个元素,将其视为局部最小值
return left;
}