题目描述:
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
示例1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例3:
输入:nums = [], target = 0
输出:[-1,-1]
方法1 c++代码 二分法+暴力寻找:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int n=nums.size();
if(n==0){
vector<int> v;
v.push_back(-1);
v.push_back(-1);
return v;
}
if(n==1){
vector<int> v;
if(target == nums[0]){
v.push_back(0);
v.push_back(0);
}
else{
v.push_back(-1);
v.push_back(-1);
}
return v;
}
int left = 0, right = n-1, flag=0;
vector<int> v;
//二分法找target
while(left<=right){
int mid = (left+right)/2;
//找到了mid处为target,向mid的左右两边找直到找到和target不一样的数,记录位置
if(nums[mid] == target){
flag = 1; //flag标志二分法是否找到了target
if(mid == 0)v.push_back(mid);
else{
//向左边找第一个和target不一样的数字的位置
for(int i=1;i<=mid;i++){
if(nums[mid-i]!=nums[mid]){
v.push_back(mid-i+1);
break;
}
else if(mid-i==0){
v.push_back(mid-i);
}
}
}
if(mid == n-1)v.push_back(mid);
else{
//向右边找第一个和target不一样的数字的位置
for(int i=1;i<=(n-1-mid);i++){
if(nums[mid+i]!=nums[mid]){
v.push_back(mid+i-1);
break;
}
else if(mid+i==n-1){
v.push_back(mid+i);
}
}
}
break;
}
if(nums[mid]>target){
right = right - 1;
}
if(nums[mid]<target){
left = left + 1;
}
}
if(flag == 0){
v.push_back(-1);
v.push_back(-1);
}
return v;
}
};
正常使用二分法,先找到target(在mid处找到target),然后从mid处向左右两边查找,直到找到与target不同的位置停止,记录左右位置。
该方法速度较慢。
方法2 c++代码 使用两次二分法:
class Solution {
public:
//返回大于等于target的靠最右边的数位置
int solve1(vector<int> nums,int target,int n){
int left = 0, right = n-1, flag = 0, index = -1;
while(left<=right){
int mid = (left+right)/2;
if(nums[mid] >= target){
right = right - 1;
index = mid;
}
else if(nums[mid] < target){
left = mid + 1;
}
}
return index;
}
//返回大于target的靠最左边的数位置
int solve2(vector<int> nums,int target,int n){
int left = 0, right = n-1, flag = 0, index = -1;
while(left<=right){
int mid = (left+right)/2;
if(nums[mid] > target){
right = right - 1;
index = mid;
}
else if(nums[mid] <= target){
left = mid + 1;
}
}
return index;
}
//先找大于等于target的最靠右的数, 再找小于target的最靠左的数,两个中间即为等于target的区间
vector<int> searchRange(vector<int>& nums, int target) {
int n = nums.size();
vector<int> v;
if(n==0){
v.push_back(-1);
v.push_back(-1);
return v;
}
int n1 = solve1(nums,target,n);
int n2 = solve2(nums,target,n);
//n1=-1即大于等于target的数找不到,不可能有等于target的数,返回【-1,-1】;
//n1=n2即大于等于target的数和大于target的数是同一个,说明只满足大于target,而没有等于target的数,返回【-1,-1】
if(n1 == -1 || n1==n2){
v.push_back(-1);
v.push_back(-1);
}
else if(n2==-1){ //n1!=-1,若n2=-1,说明没有比target大的数字,即从n1向后所有数字都等于target
v.push_back(n1);
v.push_back(n-1);
}
else if(n2!=-1){ //n1!=-1,若n2!=-1,说明n2处数字开始大于target,即从n1到(n2-1)的所有数字等于target
v.push_back(n1);
v.push_back(n2-1);
}
return v;
}
};
总结:
特殊二分法:
(1)当给出的序列不满足**二分法的使用条件“有序”**时,创造有序的条件以使用二分法。
(2)传统二分法用等于作为边界条件,当要求的结果不能只用一次二分法求出,就改变二分法的边界条件,使用多次二分法得到多个结果(此题中使用两次二分法,一个用大于等于作为边界条件,一个用大于作为边界条件,得到两个结果以表示一个区间)。
(3)使用常数次二分法,时间复杂度仍为O(logn)。