一、题目
给定一个按照升序排列的整数数组 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、二分查找法
思路:
排序数组nums
中所有数字target
形成一个窗口,记窗口的左/右边界索引分别为left
和right
,分别对应窗口左边/右边的首个元素。
数字target
出现次数可以转化为:二分法分别找到左边界left
和有边界right
,易得数字target
的数量为right-left-1
。
算法解析:
- 初始化:左边界
i = 0
,有边界j = len(nums)-1
。 - 循环二分:当闭区间
[i, j]
无元素时跳出:
2.1. 计算中点m = (i+j)/2
(向下取整)
2.2. 若nums[m]<target
,则target在闭区间[m+1, j]
中,因此执行i = m+1
;
2.3. 若nums[m]>target
,则target在闭区间[i, m-1]
中,因此执行j = m-1
;
2.4. 若nums[m]=target
,则右边界right在区间[m+1, j]
中;左边界left在闭区间[i, m-1]
中。因此分为以下两种情况:
[1]. 若查找右边界right,则执行i = m+1
;(跳出时i指向右边界)
[2]. 若查找左边界left,则执行j = m-1
;(跳出 j 指向左边界) - 返回值。应用两次二分,分别查找
right
和left
,最终返回right-left-1
即可。
代码-版本1:
class Solution {
public int search(int[] nums, int target) {
// 搜索右边界 right
int i = 0, j = nums.length - 1;
while(i <= j) {
int m = (i + j) / 2;
if(nums[m] <= target) i = m + 1;
else j = m - 1;
}
int right = i;
// 若数组中无 target ,则提前返回
if(j >= 0 && nums[j] != target) return 0;
// 搜索左边界 right
i = 0; j = nums.length - 1;
while(i <= j) {
int m = (i + j) / 2;
if(nums[m] < target) i = m + 1;
else j = m - 1;
}
int left = j;
return right - left - 1;
}
}
时间复杂度: O ( l o g n ) O(logn) O(logn)
空间复杂度: O ( 1 ) O(1) O(1)
上面代码可读性较差,可优化到下面版本:
代码-版本2:
class Solution {
public int search(int[] nums, int target) {
return helper1(nums, target) - helper2(nums, target) - 1;
}
public int helper1(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] <= target) left = mid + 1;
else right = mid - 1;
}
return left;
}
public int helper2(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] < target) left = mid + 1;
else right = mid - 1;
}
return right;
}
}
上面代码颇为冗余,可以进一步优化:
代码-版本3:
class Solution {
public int search(int[] nums, int target) {
return helper(nums, target) - helper(nums, target - 1);
}
int helper(int[] nums, int tar) {
int i = 0, j = nums.length - 1;
while(i <= j) {
int m = (i + j) / 2;
if(nums[m] <= tar) i = m + 1;
else j = m - 1;
}
return i;
}
}
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)