算法简介
二分查找(Binary Search)是一种常见的查找算法,它适用于已经排序好的数组或列表。它的基本思想是不断地将待查找区间分成两半,并通过比较目标值与中间元素的大小关系来确定目标值在哪一半中,从而缩小查找范围。这个过程一直重复,直到找到目标值或者确定目标值不存在为止。
基本条件:有序无重复
基本步骤:
- 初始化左右边界,通常为数组的起始和结束索引。
- 在循环中,计算中间位置索引,以及中间位置的值。
- 根据中间位置的值与目标值的大小关系,缩小搜索范围:
- 如果中间值等于目标值,则找到了目标值,返回结果。
- 如果中间值大于目标值,则说明目标值在左半部分,将搜索范围缩小为左半部分。
- 如果中间值小于目标值,则说明目标值在右半部分,将搜索范围缩小为右半部分。
- 重复步骤 2 和步骤 3,直到找到目标值或者搜索范围为空。
常见的二分算法有两种写法:左闭右闭
,左闭右开
左闭右闭
int binarySearch(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid; // 找到目标值,返回索引
} else if (nums[mid] < target) {
left = mid + 1; // 缩小搜索范围为右半部分
} else {
right = mid - 1; // 缩小搜索范围为左半部分
}
}
return -1; // 搜索范围为空,未找到目标值
}
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
左闭右开
最右边的不包含在内,所以注意三点
- 循环条件:left == rifght无意义了
- 初始化的时候right无需-1
- 目标在在右边区间的时候,只需要更新right为middle就可以,因为本身就不包含
int binarySearch(vector<int>& nums, int target) {
int left = 0;
int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
int middle = left + ((right - left) >> 1);
if (nums[middle] > target) {
right = middle; // target 在左区间,在[left, middle)中
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,在[middle + 1, right)中
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
刷题
搜索插入位置
力扣题目链接
左闭右开
先贴代码
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0;
int right = nums.size();
while(left < right){
int middle = (left + right) / 2;
if(nums[middle] > target){
right = middle;
}
else if(nums[middle] < target){
left = middle + 1;
}
else{
return middle;
}
}
return left;
}
};
当循环结束时,说明目标值不存在于数组中,此时 left 表示目标值应该插入的位置。因为 left 表示第一个大于目标值的元素的索引,所以返回 left 即可。
我们对left进行更新的时候,都保证了更新后left左边的元素比目标值小,最后left == right,而right及其右边的元素肯定比目标值大
左闭右闭
先贴代码
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 初始化右边界为数组最后一个元素的索引
while (left <= right) { // 使用左闭右闭的循环条件
int middle = left + (right - left) / 2; // 计算中间位置的索引
if (nums[middle] == target) {
return middle; // 找到目标值,返回索引
} else if (nums[middle] < target) {
left = middle + 1; // 缩小搜索范围为右半部分
} else {
right = middle - 1; // 缩小搜索范围为左半部分
}
}
return left; // 搜索范围为空,未找到目标值,返回插入位置
}
};
在排序数组中查找元素的第一个和最后一个位置
力扣题目链接
先贴代码
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int lb = getLeftBorder(nums, target);
int rb = getRightBorder(nums, target);
if(lb == -1 || rb == -1){
return {-1, -1};
}
else{
return {lb, rb};
}
}
int getLeftBorder(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int leftborder = -1;
while(left <= right){
int middle = (left + right) / 2;
if(nums[middle] > target){
right = middle - 1;
}
else if(nums[middle] < target){
left = middle + 1;
}
else {
right = middle - 1;
leftborder = middle;
}
}
return leftborder;
}
int getRightBorder(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int rightborder = -1;
while(left <= right){
int middle = (left + right) / 2;
if(nums[middle] > target){
right = middle - 1;
}
else if(nums[middle] < target){
left = middle + 1;
}
else {
left = middle + 1;
rightborder = middle;
}
}
return rightborder;
}
};
这个题目对时间复杂度有要求,思考后发现可以使用两个二分查找的算法分别查找两个分别查找两个边界,我们只需要分析出来一个边界的函数就可以了
普通情况下我们正常处理,我们需要考虑的是nums[middle]==target,此时如果查找左边界,先标记一下,不然后面推出了通过left不好进行计算,我们更新right到middle前面
x 的平方根
力扣题目链接
class Solution {
public:
int mySqrt(int x) {
if (x == 0 || x == 1) {
return x;
}
int left = 1;
int right = x;
int ans = 0;
while (left <= right) {
int middle = left + (right - left) / 2;
if (middle <= x / middle) { // 判断条件改为小于等于,避免溢出
ans = middle;
left = middle + 1;
} else {
right = middle - 1;
}
}
return ans;
}
};