题目:
给一个按照升序排序的非负整数数组。这个数组很大以至于你只能通过固定的接口ArrayReader.get(k)来访问第k个数,并且你也没有办法得知这个数组有多大。
找到给出的整数target第一次出现的位置。你的算法需要在O(logk)的时间复杂度内完成,k为target第一次出现的位置的下标。
如果找不到target,返回-1。
题解:
方法1 倍增:
首先特判一下首个元素,然后设定 idx = 0 为查找的下标,jump = 1 为向后跳跃的长度,每次循环将 idx 向后移动 jump 个元素,并将 jump 翻倍,如果移动后的位置的值不小于 target,则 jump 缩小至一半。
即我们在保证每次跳跃后的 idx 的位置都小于target的前提下,倍增式地跳跃,以此保证 O(logk) 的时间复杂度。
循环终止的条件就是 jump == 0,就是说,这时 idx + 1 的位置已经不小于 target 了(此时idx位置的仍然是小于target),也就是说,到最后idx指向的元素是:最大的小于target的元素,所以返回答案前判断一下 idx + 1 位置是否为 target 即可。
public class Solution {
public int searchBigSortedArray(ArrayReader reader, int target) {
int firstElement = reader.get(0);
if (firstElement == target) {
return 0;
} else if (firstElement > target) {
return -1;
}
int idx = 0;
int jump = 1;
while (jump != 0) {
while (jump != 0 && reader.get(idx + jump) >= target) {
jump >>= 1;
}
idx += jump;
jump <<= 1;
}
if (reader.get(idx + 1) == target) {
return idx + 1;
} else {
return -1;
}
}
}
测试:
public class ArrayReader {
private static final int[] a = {2, 3, 4, 5, 5, 5, 5, 7, 7, 7, 8, 9, 9, 9};
private static final List<Integer> arr;
static {
arr = new ArrayList<>();
for (int num : a) {
arr.add(num);
}
}
public int get(int x) {
if (x >= arr.size()) {
return Integer.MAX_VALUE;
}
return arr.get(x);
}
}
public class Main {
public static void main(String[] args) {
Solution solution = new Solution();
ArrayReader reader = new ArrayReader();
int target = 5;
int r = solution.searchBigSortedArray(reader, target);
System.out.println(r);
}
}
另外,也可以 二分法 :
public class Solution {
public int searchBigSortedArray(ArrayReader reader, int target) {
int start = 0, end = 1, mid;
while (reader.get(end) < target) {
end <<= 1;
}
int result = -1;
while (start <= end) {
mid = start + ((end - start) >> 1);
if (reader.get(mid) == target) {
result = mid;
// 当前找到的不一定是第一次出现的,还要继续往左边找
end = mid - 1;
} else if (reader.get(mid) > target) {
end = mid - 1;
} else {
start = mid + 1;
}
}
return result;
}
}