题目概述
给定一个按照升序排列的整数数组 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
算法思路:
首先想到利用双指针找目标元素第一个与最后一个位置
然后利用二分查找来找左边界与右边界,即第一个与最后一个位置
1.双指针
- 左指针寻找第一个位置,右指针寻找最后一个位置
思路整理:
- 设置双指针与对应的标识flag,左指针与右指针同时遍历数组
- 当左指针遇到相应的元素,则将对应位置赋值给location数组的第一个位置,然后打上标签
- 当右指针遇到相应的元素,则将对应位置赋值给location数组的第二个位置,然后打上标签
- 如果左指针有标签,但右指针没有标签,则说明元素的第一个与最后一个位置相同,所以将location的第二个位置赋值为相应的位置
- 相反同理
复杂度分析
- 时间复杂度: O(n),n 为数组的长度
- 空间复杂度: O(1)
代码实现
public int[] searchRange(int[] nums, int target) {
int[] location = new int[]{-1,-1};
int left = 0;
int right = nums.length - 1;
boolean flagLeft = false,flagRight = false;
while(right >= left){
if(!flagLeft){
if(nums[left] == target){
flagLeft = true;
location[0] = left;
}
else
left++;
}
if(!flagRight){
if(nums[right] == target){
flagRight = true;
location[1] = right;
}
else right--;
}
if(flagRight && flagLeft){
break;
}
}
if(flagLeft && !flagRight) location[1] = location[0];
if(!flagLeft && flagRight) location[0] = location[1];
return location;
}
2.二分查找
- 两次二分查找找到左边界与右边界
思路整理
-
第一次二分查找
- 因为要寻找的是左边界,所以判断条件多了一个 “=”
- 这样在寻找时会排除因为相同元素而影响对边界的筛选(也就是找边界)
-
退出循环后,判断左指针是否超出数组边界
- 如果超出,则说明没有目标元素,直接返回location数组
- 如果没有,并且左指针对应的元素为目标元素,将其赋值到location数组的第一个位置
- 重新初始化right指针
- 因为找到左边界,所以不需要初始化左指针
-
第二次二分查找
- 步骤与第一次二分查找相同
- 因为已经进行了1与2,则说明存在目标元素。所以不需要再判断会超出数组边界
- 将对应的右边界赋值给location数组的第二个位置
复杂度分析
- 时间复杂度: O(logn),n 为数组的长度
- 空间复杂度: O(1)
代码实现
public int[] searchRange(int[] nums, int target) {
int[] location = new int[]{-1,-1};
if(nums == null || nums.length == 0) return location;
int n = nums.length;
int left = 0,right = n - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(nums[mid] >= target) right = mid - 1;
else left = mid + 1;
}
if(left >= n || nums[left] != target) return location;
location[0] = left;
right = n - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(nums[mid] <= target) left = mid + 1;
else right = mid - 1;
}
location[1] = right;
return location;
}