时间复杂度
假设运气最差时需要查找k次,每次寻找的区间会被缩小到 1 / 2 ; 1 / 4 ; 1 / 8 ; 1 / 16 ; 1 / 2 k 1/2;1/4;1/8;1/16;1/2^k 1/2;1/4;1/8;1/16;1/2k。最终找到时是范围缩小到1了,所以是 n / ( 2 k ) = 1 n/(2^k)=1 n/(2k)=1,所以最终 k = l o g 2 n 简写为 l o g n k=log_2n简写为logn k=log2n简写为logn
所以有 l o g n logn logn这个复杂度要求的可以去考虑二分法
34.在排序数组中寻找第一个和最后一个位置
具体实现
1.首先想好函数和对应的功能,并且考虑一些边界情况
应当将二分查找的逻辑放到另外的函数中去,左边界用二分查找,右边界如**果用线性查找的话会不会更快呢?**不一定,当target值的重复次数不多的时候会比重新二分查找快,但是如果target的数量为n,那么时间复杂度会上升到 o ( n ) o(n) o(n),所以做两次二分查找才能达到题目中的 o ( l o g n ) o(logn) o(logn)的要求。 2 l o g n 的复杂度也是 l o g n 2logn的复杂度也是logn 2logn的复杂度也是logn
边界情况:nums=[],即长度为0直接返回[-1,-1]
原理就是不断的移动区间,让left=right从而锁定一个数
vector<int> searchRange(vector<int>& nums, int target) {
if (nums.size()==0) return vector<int> {-1,-1}; //不要写[-1,-1]因为要求要是一个vector;
int begin_position = binarySearch_begin(nums,target);
if (begin_position==-1) return vector<int> {-1,-1};
int end_position = binarySearch_end(nums,target);
return vector<int> {begin_position,end_position};
}
2.编写开始位置的二分查找
要注意:
- 退出条件 可以是left=right 写作while(left<right),或者left>right 写作while(left<=right)
- 二分法的移动规则,移动后区间的开闭要思考清楚
- 算法是否保证能找到target,如果不能请加入判断
int binarySearch_begin(vector<int>& nums,int target){ // 为什么是&?
//开始位置的二分查找
// 1.初始化左右边界
int left=0,right=nums.size()-1;
// 2.确定退出二分查找条件:左右指针与mid相等
while(left<right){ // 确保在退出循环的时候left一定等于right
int mid=(left+right)/2; // 或者用位移
// 注意是nums[mid],而不是mid说明区间需要右移,left移动[mid+1,right]
if(nums[mid]<target) left=mid+1;
// 说明 第一个 target肯定不在mid右边,即target位置<=mid,所以区间变成[left,mid]
else if(nums[mid]==target) right=mid;
// nums[mid]>target,说明target肯定不在mid的右边,变为[left,mid-1]
else right=mid-1;
}
// 条件只能保证left==right,不能保证一定找到了target
if(nums[left]==target) return left;
else return -1;
}
3.结束位置的二分查找
要注意:
- 如果转移的条件是:
left=mid
那么mid
的条件就是mid=(left+right+1)/2
这个是在实际跑代码的时候才注意到的,例如left=4,mid=5会进入死循环,nums={5,7,7,8,8,10},target=8的情况下。因为left此时等于mid,(right+left)/2也等于mid,所以就一直循环,要加一的话才可以。
所以在刷题时确定开闭区间,要思考如果最后区间只剩下一个数或者两个数,自己的写法是否会陷入死循环,如果某种写法无法跳出死循环,则考虑尝试另一种写法。
4.完整代码
class Solution {
public:
int binarySearch_begin(vector<int>& nums,int target){ // 为什么是&?
//开始位置的二分查找
// 1.初始化左右边界
int left=0,right=nums.size()-1;
// 2.确定退出二分查找条件:左右指针与mid相等
while(left<right){ // 确保在退出循环的时候left一定等于right
int mid=(left+right)/2; // 或者用位移
// 注意是nums[mid],而不是mid说明区间需要右移,left移动[mid+1,right]
if(nums[mid]<target) left=mid+1;
// 说明 第一个 target肯定不在mid右边,即target位置<=mid,所以区间变成[left,mid]
else if(nums[mid]==target) right=mid;
// nums[mid]>target,说明target肯定不在mid的右边,变为[left,mid-1]
else right=mid-1;
}
// 条件只能保证left==right,不能保证一定找到了target
if(nums[left]==target) return left;
else return -1;
}
int binarySearch_end(vector<int>& nums,int target){ // 为什么是&?
//结束位置的二分查找
// 1.初始化左右边界
int left=0,right=nums.size()-1;
// 2.确定退出二分查找条件:左右指针与mid相等
while(left<right){ // 确保在退出循环的时候left一定等于right
int mid=(left+right+1)/2; // !!如果left=mid则mid=(l+r+1)
// 注意是nums[mid],而不是mid说明区间需要右移,left移动[mid+1,right]
if(nums[mid]<target) left=mid+1;
// 说明 最后 target肯定不在mid右边,即target位置>=mid,所以区间变成[mid,right]
else if(nums[mid]==target) left=mid;
// nums[mid]>target,说明target肯定不在mid的右边,变为[left,mid-1]
else right=mid-1;
}
// 条件只能保证left==right,不能保证一定找到了target
return right;
// 不需要,因为找开始位置的时候验证过了,else return -1;
}
vector<int> searchRange(vector<int>& nums, int target) {
if (nums.size()==0) return vector<int> {-1,-1}; //不要写[-1,-1]因为要求要是一个vector;
int begin_position = binarySearch_begin(nums,target);
if (begin_position==-1) return vector<int> {-1,-1};
int end_position = binarySearch_end(nums,target);
return vector<int> {begin_position,end_position};
}
};
也可以写成:
int lower_bound(vector<int> &nums, int target) {
int l = 0, r = nums.size(), mid;
while (l < r) {
mid = (l + r) / 2;
if (nums[mid] >= target) {
r = mid;
} else {
l = mid + 1;
} }
return l;
}
// 辅函数
int upper_bound(vector<int> &nums, int target) {
int l = 0, r = nums.size(), mid;
while (l < r) {
mid = (l + r) / 2;
if (nums[mid] > target) {
r = mid;
} else {
l = mid + 1;
}
}