目录
题目要求
给你一个按照非递减顺序排列的整数数组 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]
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109来源:力扣(LeetCode)
这道题用的方法跟上一篇博客一样都是用的二分法,但是仔细看题目却又是一头雾水,没关系万事开头难,只要我们把不会的东西搞懂,就变成了自己的东西,加油!
题目理解和分析
(一) 老规矩了,看题先读题,看懂了题目你就解对了一半了。这个问题首先以看,排序数组而且非递减数组,说明什么?说明题目给的例子都是升序排列的数组。再想想看看有几个条件已知:升序排列 + 数组 + 寻找已定目标数据 = 二分法。
(二) 相信通过前面的二分法讲解,大多数读者都能对二分法有很好的理解。如果不知道什么是二分法的读者可以参考上一篇博客,这里就不过多的解释了。众所周知二分法有一个先决条件——升序或者降序排序的数组。很显然这一点题目已经满足了,下面便开始思路的建立。
(三) 二分法使用思路简单,但是在细节处理上确实魔鬼级别。所以明白了二分法的大体建立后,我们要不断的去画图理解题目,以此来设立一个简单高效的细节处理。
(四) 之前遇到的都是寻找一个目标数据,但是这次却要求找到两个目标数据的位置,而其最为关键的是这两个数据必须是整个数组中第一个和最后一个的位置。这就让很多读者犯难了,二分法本身就是不断尝试判断缩小范围的算法,你却让我找到具体的位置,这不是为难人吗?
(五) 不用怕,记住一个事物最不同寻常的最关键的点往往是解决问题的捷径。所以这道题我们就从第一个和最后一个位置这个关键点入手。入手前我们需要找到一个规律,下面我们举个例子来仔细观察:
观察上面的图解,我们可以发现什么?
1.寻找第一个大于等于 target 的数并记录其位置为 left_ID(记住,一定是大于等于不能是等于,为什么?因为要考虑到整个数组中都没有我们要找的 target 的值的情况)
2.寻找第一个大于 target 的数并记录其位置为 right_ID(记住,一定是大于,为什么?因为这里我们要找的是 target 在数组中的最后一个位置),找到这个位置后将其 -1 ,千万不能忘记 -1 想想为什么?
(六) 经过上面的寻找后,我们在将其进行一系列的判断,如果符合所有的条件,我们就将其返回,那么本题到此也就结束了。
相信很多读者看到这仍然有一些或大或小的疑惑,没关系下面我们结合具体的代码进行讲解
代码分部讲解
第一部分
int LeftSearch(int* nums, int numsSize, int target)
{
int right_ID = numsSize - 1,left_ID = 0,ans = numsSize,mid;
while(left_ID <= right_ID)
{
mid = (right_ID + left_ID) / 2;
if(nums[mid] == target)
{
right_ID = mid -1;
ans = mid;
}
else if(nums[mid] > target)
{
right_ID = mid - 1;
}
else
{
left_ID = mid + 1;
}
}
return ans;
}
在讲解这一部分之前,我们先来做个图解进行分析一下:
这个图解便是整个第一部分的内容,其目的就是求出 target 在数组中的第一个位置。
这一部分中有可以化简的地方:
while(left_ID <= right_ID) { mid = (right_ID + left_ID) / 2; if(nums[mid] >= target) { right_ID = mid -1; ans = mid; } else { left_ID = mid + 1; } }
就可以化成这个样子,但是为了便于大家理解,所以上面把 >= 分别拆成了 == 和 > 两个部分
第二部分
int RightSearch(int* nums, int numsSize, int target)
{
int right_ID = numsSize - 1,left_ID = 0,ans = numsSize,mid;
while(left_ID <= right_ID)
{
mid = (right_ID + left_ID) / 2;
if(nums[mid] > target)
{
right_ID = mid - 1;
ans = mid;
}
else
{
left_ID = mid + 1;
}
}
return ans;
}
这一部分的目的是求出第一个大于 target 的数的位置。思路和步骤跟第一部分一致,这里就不过多的讲解了。
第三部分
int* searchRange(int* nums, int numsSize, int target, int* returnSize){
int left, right; //定义
left = LeftSearch(nums, numsSize, target); 。//调用函数
right = RightSearch(nums, numsSize, target) - 1; //调用函数
int* ret = malloc(sizeof(int) * 2); //申请空间,进行动态分配
*returnSize = 2;
//一系列判断
if (left <= right && right < numsSize && nums[left] == target && nums[right] == target)
{
ret[0] = left;
ret[1] = right;
return ret; //返回
}
ret[0] = -1;
ret[1] = -1;
return ret; //返回
}
这一部分就相对来说很简单了,这里就讲解一些关键的地方
right = RightSearch(nums, numsSize, target) - 1;
这里需要注意的就是 -1 ,至于原因上面已经讲过了,因为我们 RightSearch 函数中所求的位置是第一个大于 target 的数的位置,而非是最后一个 target 的位置,所以我们需要 -1
if (left <= right && right < numsSize && nums[left] == target && nums[right] == target)
这个就是前面说的,仅仅找到了对应的 left 和 right 的值还不行,还要进行判断,看看所求的值是不是符合要求。
第四部分
附上完整的代码
int LeftSearch(int* nums, int numsSize, int target)
{
int right_ID = numsSize - 1,left_ID = 0,ans = numsSize,mid;
while(left_ID <= right_ID)
{
mid = (right_ID + left_ID) / 2;
if(nums[mid] == target)
{
right_ID = mid -1;
ans = mid;
}
else if(nums[mid] > target)
{
right_ID = mid - 1;
}
else
{
left_ID = mid + 1;
}
}
return ans;
}
int RightSearch(int* nums, int numsSize, int target)
{
int right_ID = numsSize - 1,left_ID = 0,ans = numsSize,mid;
while(left_ID <= right_ID)
{
mid = (right_ID + left_ID) / 2;
if(nums[mid] > target)
{
right_ID = mid - 1;
ans = mid;
}
else
{
left_ID = mid + 1;
}
}
return ans;
}
int* searchRange(int* nums, int numsSize, int target, int* returnSize){
int left, right;
left = LeftSearch(nums, numsSize, target);
right = RightSearch(nums, numsSize, target) - 1;
int* ret = malloc(sizeof(int) * 2);
*returnSize = 2;
if (left <= right && right < numsSize && nums[left] == target && nums[right] == target)
{
ret[0] = left;
ret[1] = right;
return ret;
}
ret[0] = -1;
ret[1] = -1;
return ret;
}
总结
本题的主旨方法还是熟悉的——二分法,但是正如前面所说,二分法固然使用思路简单,但是对于细节处理确实魔鬼般的存在。对于本文实现的代码(尤其是求 left 和 right 位置的两个函数),希望大家一定自己用手画画想想,这样才能理解的更加深刻。