根据题意,显然需要使用二分查找解决问题,下面给出 递增数组 的二分查找模板
注:使用二分查找时,数组必须有序
public int binarySearch(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
// mid用这种定义方式,数据不会溢出
int mid = left + (right - left) / 2;
if (target > nums[mid]) {
// 数组递增,target比当前值大,到右半边寻找
left = mid + 1;
} else if (target < nums[mid]) {
// 数组递增,target比当前值小,到左半边寻找
right = mid - 1;
} else {
// 找到数据的下标并返回
return mid;
}
}
// 未找到,返回-1
return -1;
}
将二分查找引入,先找到数组中等于target的元素的下标,然后使用双指针,一个指针向左,一个指针向右,分别找到最左边的target和最右边的target,将这两个下边返回即可。
如果没有找到,直接返回{-1, -1}
class Solution {
public int[] searchRange(int[] nums, int target) {
int length = nums.length, i = 0, j = length - 1, index = 0;
boolean flag = false;
// 时间复杂度为O(logN),结合数组升序(有序),显然需要使用二分查找,找到target的下标
while (i <= j) {
int mid = (i + j) / 2;
if (target > nums[mid]) {
i = mid + 1;
} else if (target < nums[mid]) {
j = mid - 1;
} else {
flag = true;
// 记录target的下标
index = mid;
break;
}
}
if (!flag) return new int[]{-1, -1};
// 从target的下标开始向左与向右搜索,直到值不等于target
i = j = index;
while (i >= 0 && nums[i] == target) i--;
while (j < length && nums[j] == target) j++;
return new int[]{i + 1, j - 1};
}
}
然而,根据上面的处理,我们会发现时间复杂度并不是简单的O(logN),因为还引入了左右两个指针。假设一种最极端的情况,整个数组中所有的元素值都为target,那么左右指针还是会完整遍历整个数组,时间复杂度依然是O(N)。因此,需要想一个办法,继续使用二分查找来寻找符合条件的target
那么如何才能找到target的时候不返回,而是继续向左或者向右寻找符合条件的target呢?这里可以分别使用两个二分查找: 一个不断向左寻找,找到最左边的target; 一个负责向右寻找,找到最右边的target
这里又引申出了新的问题,怎么样才能找到最左边或最右边的target?
解决办法其实很简单,以寻找最左边的target为例,当 nums[mid] == target 时,先不要急着返回,看看nums[mid - 1]是不是和nums[mid]相等(同时要求mid > 0,如果 mid == 0,说明当前下标是数组第一个元素,自然不用继续往前寻找),如果相等,说明对于当前的mid位置,并不是最左边的target,也就是说,target需要向左寻找,也就是说,寻找的区间要放在左边,相当于 right = mid - 1
同理,向右寻找时,如果找到了 nums[mid] == target 时,不要急着返回,看看nums[mid + 1]和nums[mid]是否相等(同时要求mid < length - 1,若mid == length - 1,说明当前mid指向的元素是数组中最后一个元素,也就是最右边的target),如果相等,就要继续向右寻找,寻找区间要移到右边,等价于left = mid + 1
综上,我们对上面的代码进行修改,使用两个binarySearch找到左右边界
public int[] searchRange(int[] nums, int target) {
int left = leftBinarySearch(nums, target);
int right = rightBinarySearch(nums, target);
return new int[]{left, right};
}
private int leftBinarySearch(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (target > nums[mid]) {
left = mid + 1;
} else if (target < nums[mid]) {
right = mid - 1;
} else {
// 如果mid是数组的第一个元素或nums[mid - 1] != nums[mid],说明当前mid指向的就是最左边的target
if (mid == 0 || nums[mid - 1] != nums[mid]) return mid;
// 左边还有target,继续向左区间寻找
right = mid - 1;
}
}
return -1;
}
private int rightBinarySearch(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (target > nums[mid]) {
left = mid + 1;
} else if (target < nums[mid]) {
right = mid - 1;
} else {
// 如果mid是数组最右边的元素或nums[mid + 1] != nums[mid],说明当前mid指向的就是最右边的target
if (mid == nums.length - 1 || nums[mid + 1] != nums[mid]) return mid;
// 右边还有target,继续向右区间寻找
left = mid + 1;
}
}
return -1;
}
本题得解