1.二分查找简介
二分查找算法(Binary Search)是一种高效的查找算法,适用于有序数组或序列。它的基本思想是通过逐步缩小查找范围,将查找区间一分为二,直到找到目标值或确定目标值不存在。
算法原理:在数组的中间位置选择一个元素作为"中间值 "。将目标值与"中间值"进行比较:如果目标值等于 中间值,查找成功,返回中间值的索引。如果目标值小于 中间值,则在左半部分继续查找。如果目标值大于中间值,则在右半部分继续查找。重复以上步骤,直到找到目标值或区间为空(即找不到目标值)。
时间复杂度:二分查找的时间复杂度为 (O(\log n)),其中 (n) 是数组的元素个数。每次查找将查找范围缩小一半,因而效率较高。
在二分查找中,针对特定需求,常用的有以下三种方式:
(1)朴素二分查找:查找任意一个等于目标值的位置。
朴素二分查找模版
cpp
while (left <= right)//循环条件
{
int mid = left + (right - left) / 2;//left + (right - left + 1) / 2也可
if (...)
left = mid + 1;
else if (...)
right = mid - 1;
else
return ...
}
(2)左端点二分查找:查找目标值的最左位置(即第一个出现的目标值)。
二分查找区间左端点
cpp
while (left < right)//判断条件不能等于
{
int mid = left + (right - left) / 2;//只能是left + (right - left) / 2
if (...) left = mid + 1;
else right = mid;
}
(3)右端点二分查找:查找目标值的最右位置(即最后一个出现的目标值)。
二分查找区间右端点
cpp
//找右端点
while (left < right)//判断条件不能等于
{
int mid = left + (right - left + 1) / 2;//只能是left + (right - left + 1) / 2
if (...) left = mid;
else right = mid - 1;
}
二分查找不仅适用于有序数组 ,还适用于具有二段性的情况。二段性指的是一个序列被划分成两段:在某个位置之前满足某个条件,而在该位置之后不满足(或反之)。利用二分查找,能够高效地找到这个转折点。
2.二分查找
- 题目简介
- 算法思想
暴力解法:遍历数组寻找目标值
二分查找:数组是升序的
(1)循环结束条件:left <= right
(2)中间值小于目标值,去右区间查找:mid < target -> left = mid + 1
中间值大于目标值,去左区间查找:mid > target -> right = mid - 1
中间值等于目标值:返回
- 代码实现
cpp
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left <= right)
{
int mid = left + (right - left)/2;//可防止溢出
if (nums[mid] < target) left = mid + 1;
else if (nums[mid] > target) right = mid - 1;
else if (nums[mid] == target) return mid;
}
return -1;
}
};
3.在排序数组中查找元素的第⼀个和最后⼀个位置
- 题目简介
- 算法思想
暴力解法:遍历数组,找到符合条件的区间
二分查找:有序数组,可以用二分查找,使用左右端点二分查找找到符合条件的区间的左右端点
(1)循环结束条件:left < right
(2)找左端点
中间值小于目标值,找大:mid < target -> left = mid + 1
中间值大于目标值,找小:mid >= target -> right = mid
(3)找右端点
中间值小于目标值,找大:mid <= target -> left = mid
中间值大于目标值,找小:mid > target -> right = mid - 1
- 代码实现
cpp
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
if (nums.size() == 0) return {-1, -1};
int begin = 0;
int left = 0, right = nums.size() - 1;
//找左端点
while (left < right)
{
int mid = left + (right - left) / 2;
if (nums[mid] < target) left = mid + 1;
else right = mid;
}
if (nums[right] != target) return {-1, -1};
else begin = left;
right = nums.size() - 1;
//找右端点
while (left < right)
{
int mid = left + (right - left + 1) / 2;
if (nums[mid] <= target) left = mid;
else right = mid - 1;
}
return {begin, right};
}
};
4. x的平安根
- 题目简介
- 算法思想
暴力解法:从1开始计算其平方,找到第一个平方大于target的数
二分查找:从1开始到x区间使用二分查找法,返回值左区间的元素平方小于等于x,右区间元素平方大于x。
(1)循环结束条件:left < right
(2)小于目标值,找大:mid * mid <= x -> left = mid
大于目标值,找小:mid * mid > x -> right = mid - 1
- 代码实现
cpp
class Solution {
public:
int mySqrt(int x) {
if (x < 1) return 0;
int left = 1, right = x;
while (left < right)
{
long long mid = left + (right - left + 1) / 2;
if (mid * mid <= x) left = mid;
else right = mid - 1;
}
return left;
}
};
5. 搜索插入位置
- 题目简介
- 算法思想
暴力解法:遍历数组找到第一个比目标值大的元素的下标
二分查找:数组是升序的,我们要找的返回值是大于等于target的,因此将数组分为左右两个区间,左边区间小于target,右边区间大于等于target,,所以我们要找到右区间的左端点。
(1)循环结束条件:left < right
(2)中间值小于目标值,则应该找大:mid < target -> left = mid + 1
中间值大于目标值,找小:mid > target -> right = mid
cpp
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left < right)
{
int mid = left + (right - left) / 2;
if (nums[mid] < target) left = mid + 1;
else right = mid;
}
if (nums[left] < target) return left + 1;
else return left;
}
};
6. 山脉数组的封顶索引
- 题目简介
暴力解法:遍历数组寻找峰值
二分查找:数组具有二段性,arr[i] < arr[i + 1],arr[i] > arr[i+ 1]
(1)循环结束条件:left < right
(2)处于升序部分:arr[mid] >= arr[mid - 1] left = mid;
处于降序部分:arr[mid] < arr[mid - 1] right = mid - 1;
- 代码实现
cpp
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]) left = mid;
else right = mid - 1;
}
return left;
}
};
7. 寻找峰值
- 题目简介
-
算法思想
-
代码实现
暴力解法:遍历数组,找到一个大于左右相邻值的元素
二分查找算法:找二段性,nums[-1] = nums[n] = -∞,arr[i] > arr[i + 1] 则左边一定存在峰值,
arr[i] < arr[i + 1] 则右边一定存在峰值。
(1)循环结束条件: left < right
(2)处于递减序列中:nums[mid] > nums[mid + 1] -> right = mid;
处于递增序列中:nums[mid] <= nums[mid + 1] -> left = mid + 1
cpp
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int left = 0, right = nums.size() - 1;
while (left < right)
{
long long mid = left + (right - left) / 2;
if (nums[mid] > nums[mid + 1]) right = mid;
else left = mid + 1;
}
return right;
}
};
8.搜索旋转排序数组中的最小值
- 题目简介
- 算法思想
暴力解法:遍历数组找到最小值
二分查找:找二段性,AB之间大于数组最后一个元素,CD之间小于等于D元素
(1)循环条件:left < right
(2)中间值大于D:left = mid + 1
中间值小于等于D:right = mid
- 代码实现
cpp
class Solution {
public:
int findMin(vector<int>& nums) {
int n = nums.size();
int left = 0, right = n - 1;
while (left < right)
{
int mid = left + (right - left) / 2;
if (nums[mid] > nums[n - 1]) left = mid + 1;
else right = mid;
}
return nums[left];
}
};
9. 0〜n-1 中缺失的数字
- 题目简介
- 算法思想
暴力解法:遍历数组,时间复杂度为O(n)
二分查找:找二段性,目标元素左边区间的元素和它们的下标相等,右边区间的元素比他们的下标大1
(1)循环结束条件:left < right
(2) 元素和下标相等:records[mid] == mid -> left = mid + 1;
不相等:right = mid;
- 代码实现
cpp
class Solution {
public:
int takeAttendance(vector<int>& records) {
int left = 0, right = records.size() - 1;
while (left < right)
{
int mid = left + (right - left) / 2;
if (records[mid] == mid) left = mid + 1;
else right = mid;
}
if (records[left] == left) return left + 1;
else return left;
}
};