有效地查找已排序数组中的整数量

时间:2014-04-02 14:29:51

标签: java algorithm time-complexity binary-search

我正在学习考试,并发现了这个问题。

您将获得一个排序的整数数组,例如:

{-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

我需要尽可能高效地写它,请不要给我答案 (或者如果你愿意的话写下来,但我不会看),我的想法是做二分搜索,然后 转到我找到的值的两边(向后和向前)以及索引号,返回正确的答案,我的问题是:

  1. 这是最有效的方式吗?
  2. 在最坏的情况下赢了O(n)? (当数组填充一个数字时) -
  3. 如果是这样 - 那我为什么要进行二分搜索呢?

5 个答案:

答案 0 :(得分:15)

修改二进制搜索以查找给定输入的第一个和最后一个匹配项,然后这两个索引之间的差异就是结果。

要使用二进制搜索查找第一个和最后一个匹配项,您需要更改通常的二进制搜索算法中的位。在二进制搜索中,找到匹配项时返回值。但是,与通常的二进制搜索不同,您需要继续搜索,直到找到不匹配。

有用的链接

finding last occurencefinding 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个指数之间的数字数。

Working code:

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)。