1.题目
输入一个排好序的整数数组,找到指定目标数的开始和结束位置。如果指定的数字不在数组中,则输出 [-1,-1]。例如,输入数组为[5, 7, 7, 8, 8, 10], 目标数为8, 输出[3, 4].本题会人工判题,要求时间复杂度O(logn) (来源于牛客网欢聚时代在线笔试编程题)
2.解法
使用二分法寻找目标数的开始和结束位置(注:这里在用二分法找到目标数后不能用线性遍历的方式找开始或结束位置,原因详见代码注释)。
这个问题的考察点一个是二分查找的使用,另外是边值情况是否考虑周全。
import java.util.Scanner;
public class Test1 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int target = scanner.nextInt();
scanner.nextLine();
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = scanner.nextInt();
}
// begin和end分别为数组的起始和结束位置
int begin = 0;
int end = n - 1;
// beginFoundLocation记录找到的目标值第一次出现的地方
int beginFoundLocation = -1;
// endFoundLocation 记录找到的目标值最后一次出现的地方
int endFoundLocation = -1;
endFoundLocation = findLastLocation(begin, end, arr, target);
beginFoundLocation = findFirstLocation(begin, end, arr, target);
System.out.println("[" + beginFoundLocation + "," + endFoundLocation + "]");
}
// 在arr数组的begin到end的范围内,寻找target第一次出现的位置
public static int findFirstLocation(int begin, int end, int[] arr, int target) {
// 记录中间点的位置及其值
int middle = 0;
int middleValue = arr[0];
while (begin <= end) {
/*
* 二分查找:当中间位置点的值比target要小的时候就将寻找的起始位置(begin)设置为中间位置的下一个位置,
* 当中间位置点的值比target要大的时候就将寻找的终止位置(end)设为中间位置的下一个位置
*/
middle = begin + (end - begin) / 2;
middleValue = arr[middle];
/*
* 注意:当中间位置的点的值正好是target的时候,需要看该位置的前一个位置点的值是否也为target,这就分为以下三种情况
* 1.可能此时中间位置已经是0,也就是指向了数组的第一个元素,它没有前一个位置点,说明target第一次出现的位置就是0
* 2.中间位置不是0,而且中间位置的前一个位置的值也是target,则继续用二分法在前面查找target
* (注:这里不能用线性探测的方法,因为当输入数组元素值全部正好是target的时候,就成了遍历整个数组前半段,
* 同理找最后target出现的位置也不能用线性探测法)。
* 3.中间位置不是0,而且中间位置的前一个位置的值不是target,则正好该中间位置就是target第一次出现的位置,返回就好了。
*/
if (middleValue < target) {
begin = middle + 1;
} else if (middleValue > target) {
end = end - 1;
} else if (middle > 0 && arr[middle - 1] == target) {
end = middle - 1;
} else if (middle > 0 && arr[middle - 1] != target) {
return middle;
} else if (middle == 0) {// 这里需要判定下,因为会有begin=end=0的情况,会死循环。
return 0;
}
}
// 当循环条件不满足的时候,可能是找到了target第一次出现的位置,也可能是target根本不在数组中
if (arr[middle] != target) {
return -1;
} else {
return middle;
}
}
// 寻找target最后出现的位置,具体原理和寻找最初出现的位置相似
public static int findLastLocation(int begin, int end, int[] arr, int target) {
int middle = 0;
int middleValue = arr[0];
int lastArr = arr.length - 1;
while (begin <= end) {
middle = begin + (end - begin) / 2;
middleValue = arr[middle];
if (middleValue < target) {
begin = middle + 1;
} else if (middleValue > target) {
end = end - 1;
} else if (middle < lastArr && arr[middle + 1] == target) {
begin = middle + 1;
} else if (middle < lastArr && arr[middle + 1] != target) {
return middle;
} else if (middle == lastArr) {// 同样是为了防止死循环,需要判断下
return lastArr;
}
}
if (arr[middle] != target) {
return -1;
} else {
return middle;
}
}
}