我正在学习考试,并发现了这个问题。
您将获得一个排序的整数数组,例如:
{-5, -5, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 67, 67, 99}
写一个方法:
Public static int count (int[] a, int x)
返回次数,数字' x'在数组中。
例如:
x = -5, it returns 2
x = 2, it returns 5
x = 8, it returns 0
我需要尽可能高效地写它,请不要给我答案 (或者如果你愿意的话写下来,但我不会看),我的想法是做二分搜索,然后 转到我找到的值的两边(向后和向前)以及索引号,返回正确的答案,我的问题是:
如果是这样 - 那我为什么要进行二分搜索呢?
答案 0 :(得分:15)
修改二进制搜索以查找给定输入的第一个和最后一个匹配项,然后这两个索引之间的差异就是结果。
要使用二进制搜索查找第一个和最后一个匹配项,您需要更改通常的二进制搜索算法中的位。在二进制搜索中,找到匹配项时返回值。但是,与通常的二进制搜索不同,您需要继续搜索,直到找到不匹配。
有用的链接
finding last occurence,finding first occurance
稍微更新
找到第一个匹配项后,您可以使用该索引作为下一个二进制搜索的起点来查找最后一个。
答案 1 :(得分:5)
我想到了两个解决方案:
1) 二元搜索是否正常,但保持它发现第一次出现的不变量。然后进行线性搜索。这将是Theta(log n + C),其中C是计数。
由Jon Bentley撰写的编程珍珠有一个很好的写作,他提到寻找第一次出现实际上比寻找任何出现更有效。2) 您还可以进行两次二进制搜索,一次是第一次出现,另一次是最后一次,并取得索引的差异。这将是Theta(log n)。
您可以根据C的预期值来决定应用哪种解决方案。如果C = o(log n)(是小o),那么寻找第一次出现并进行线性搜索会更好。否则进行两次二进制搜索。
如果您不知道C的预期值,那么使用解决方案2可能会更好。
答案 2 :(得分:5)
进行二元搜索以找到第一次出现。进行二分查找以找到最后一次出现。出现次数等于找到的2个指数之间的数字数。
public class Main {
public static void main(String[] args){
int[] arr = {-5, -5, 1, 1, 1, 1, 1, 1,
1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 67, 67, 99};
int lo = getFirst(arr, -5);
if(lo==arr.length){ // the number is not present in the array.
System.out.println(0);
}else{
int hi = getLast(arr, -5);
System.out.println((hi-lo+1));
}
}
// Returns last occurence of num or arr.length if it does not exists in arr.
static int getLast(int[] arr, int num){
int lo = 0, hi = arr.length-1, ans = arr.length;
while(lo<=hi){
int mid = (lo+hi)/2;
if(arr[mid]==num){
ans = mid;
lo = mid+1;
}else if(arr[mid]<num){
lo = mid+1;
}else if(arr[mid]>num){
hi = mid-1;
}
}
return ans;
}
// Returns first occurence of num or arr.length if it does not exists in arr.
static int getFirst(int[] arr, int num){
int lo = 0, hi = arr.length-1, ans = arr.length;
while(lo<=hi){
int mid = (lo+hi)/2;
if(arr[mid]==num){
ans = mid;
hi = mid-1;
}else if(arr[mid]<num){
lo = mid+1;
}else if(arr[mid]>num){
hi = mid-1;
}
}
return ans;
}
}
答案 3 :(得分:1)
实际上有一个比给定解决方案更好的解决方案!它是两种不同的二元搜索方式的组合。
首先进行二分查找以获得第一次出现。这是O(log n)
现在,从您刚刚找到的第一个索引开始,您可以进行不同类型的二分搜索:您猜测该元素F的频率,首先猜测F = 1并将估计值加倍并检查元素是否重复。这保证是O(log F)(其中F是频率)。
这样,算法在O(log N + log F)
中运行您不必担心数字的分布!
答案 4 :(得分:0)
恕我直言,这是最有效的解决方案:其他人可能已经提到了类似的方法,但我认为这是最容易解释和最容易理解的方法,而且它还有一个修改,可以加快实践过程:< / p>
基本上,这个想法是找到最小和最大的出现指数。使用二进制搜索找到最小的O(log N)(使用牛顿方法实际上在平均情况下提高性能是可能的改进)。如果您不知道如何修改二进制搜索以找到最小的索引,那么简单的修改就是查找值为(p - 0.5)
的元素 - 显然您不会在整数数组中找到该值,但如果是二进制搜索终止索引将是递归停止的旁边的索引。您只需要检查它是否存在。这将为您提供最小的索引。
现在为了找到最大的索引,你必须再次启动二进制搜索,这次使用最小索引作为下限和(p + 0.5)
作为搜索目标,这保证是O(log N),在平均情况下它将是O(log N / 2)。使用牛顿方法并考虑上限和下限的值将在实践中提高性能。
一旦找到最大和最小的索引,它们之间的差异显然就是结果。
对于均匀分布的数字,使用牛顿修改将大大改善运行时间(在连续等距数字序列的情况下,将找到最小和最大值的O(1)(两步或三步)),虽然任意输入的理论复杂度仍为O(log N)。