一、概念
时间复杂度O(logN)
知识点:
1
求中点
mid=left+(right-left)/2 对于偶数取到中点的左边
mid=left+(right-left+1)/2 对于偶数取到中点右边
对于奇数情况取到的中点都相同
2
不一定需要有序
二分实质是根据一个特定条件将区间分为两份
3
基础
while(left<=right)
{
int mid=left+(right-left)/2;
if(...)
left=mid+1;
else if(...)
right=mid-1;
else
return ...;
}
查找左端点 : 即右区间包括ans
while(left<right)
{
int mid=left+(right-left)/2;
if(...)
left=mid+1;
else
right=mid;
}
查找右端点: 即左区间包括ans
while(left<right)
{
int mid=left+(right-left+1)/2;
if(...)
left=mid;
else
right=mid-1;
}
二、例题
1二分查找
1优化:防止溢出
int mid=left+(right-left)/2;
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0;
int right=nums.size()-1;
int mid=left+(right-left)/2;
while(left<=right)
{
mid=(left+right)/2;
if(nums[mid]>target)
right=mid-1;
else if(nums[mid]<target)
left=mid+1;
else
return mid;
}
return -1;
}
};
2在排序数组中查找元素的第一个和最后一个位置
以此题为例
1,当ans被包括在右区间 即【0,index-1】【index,size-1】
index为ans对应的下标
mid取到偶数中间的左边元素
取极限情况,当nums[mid]<target,当left为index-1时,right为index时,由于mid取到偶数中间的左边元素,则left+1就会跳出循环
2,当ans被包括在左区间 即【0,index】【index+1,size-1】index为ans对应的下标 ,同理不多赘述
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
if(nums.size()==0)
return {-1,-1};//可能是空数组
int left=0;
int right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left)/2;
if(nums[mid]<target)
left=mid+1;
else
right=mid;
}
int ans1=left;
if(nums[left]!=target)
return {-1,-1};
left=0;
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 {ans1,left};
}
};
3搜索插入位置
二分:区间区分
第一个nums[mid]>=target
注意当target大于数组中所有值时
target的值应该为size()
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
//找第一个大于等于target的下标
int left=0;
int right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left)/2;
if(nums[mid]<target)
left=mid+1;
else
right=mid;
}
//当目标值全都大于数组元素
if(target>nums[nums.size()-1])
return nums.size();
return left;
}
};
4 x 的平方根
二分:区间区分
最后一个mid*mid<=x
一开始还以为要自己创个1到n的数组,但仔细想想这样时间空间复杂度不都是On了
后来在想想直接用left=0,left一直加,right=n,right一直减不就行了
这样时间复杂度0logn,
class Solution {
public:
int mySqrt(int x) {
if(x==0)
return 0;
int left=1;//注意是从1开始
int right=x;
while(left<right)//找最后一个下表对应的值的平方小于等于x的值
{
long long mid=left+(right-left+1)/2;
if(mid*mid<=x)//这里有溢出的风险 用long
left=mid;
else
right=mid-1;
}
return left;
}
};
5山脉数组的峰顶索引**
二分:区间区分
第一个arr[mid]<arr[mid-1]
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;
}
};
6寻找峰值
和题五本质一样 因为只需要找到一个值就行
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int left=0;
int right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left+1)/2;
if(nums[mid]>=nums[mid-1])
left=mid;
else
right=mid-1;
}
return left;
}
};
7找旋转排序数组中的最小值
二分:区间区分
第一个nums[mid]<nums[0]
class Solution {
public:
int findMin(vector<int>& nums) {
if(nums[0]<nums[nums.size()-1])//特判
return nums[0];
int left=0;
int right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left)/2;
if(nums[mid]>=nums[0])//注意是大于等于 不是大于
left=mid+1;
else
right=mid;
}
return nums[left];
}
};
8点名
注意特判是最后一个取到n
二分:区间区分
第一个mid<nums[mid]
class Solution {
public:
int takeAttendance(vector<int>& records) {
if(records[records.size()-1]==records.size()-1)
return records.size();
int left=0;
int right=records.size()-1;
while(left<right)
{
int mid=left+(right-left)/2;
if(mid>=records[mid])
left=mid+1;
else
right=mid;
}
return left;
}
};