【问题标题】:efficiently find amount of integers in a sorted array有效地查找排序数组中的整数数量
【发布时间】:2014-05-13 22:12:44
【问题描述】:

我正在准备考试,发现了这个问题。

例如,给定一个排序的整数数组:

{-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) 吗? (当数组填充单个数字时)-

如果是这样 - 那我为什么要进行二分搜索?

【问题讨论】:

  • @ambigram_maker 这可能是一个“修改过的”二进制搜索,如果你找到你的密钥,你仍然会继续搜索。
  • 如果允许,请检查数组中是否存在该数字。如果没有,请返回。否则,我会说使用过早返回尽快退出该方法。
  • 一定有办法低于 O(n),这是一个关于复杂性的问题,他们不会给我一个问题,我可以去 O(n) 次,然后当我找到时数数它,在我看来是二分搜索的一种变体。
  • @SethKeno 显然您的算法必须查看数组中的每个元素。我认为它不会比 O(n) 更好。我能想到的天真的方法宁愿是 O(n^2) [对于每个元素,查看它,然后遍历数组,计算那个元素]
  • @fstd 你读过这个问题吗?即使是最简单的实现(计算一个数组中 ONE 值的出现次数也不超过 O(n)!鉴于数组已排序,这当然可以使用一些除法在 O(log(n)) 中解决和征服的方法。

标签: java algorithm time-complexity binary-search


【解决方案1】:

恕我直言,这是最有效的解决方案:其他人可能已经提到了类似的方法,但我认为这是最容易解释和最容易理解的方法,而且它有一个修改可以加快实践过程:

基本上这个想法是找到出现的最小和最大索引。使用二分搜索找到最小的是 O(log N)(使用牛顿方法实际上提高平均情况下的性能是一种可能的改进)。如果您不知道如何修改二分查找以找到最小索引,那么简单的修改是查找值为 (p - 0.5) 的元素 - 显然您不会在整数数组中找到该值,但如果二分查找终止该索引将是递归停止位置旁边的索引。您只需要检查它是否存在。这将为您提供最小的索引。

现在为了找到最大的索引,你必须再次开始二分搜索,这次使用最小的索引作为下限(p + 0.5)作为搜索目标,这是保证为 O(log N),在平均情况下它将为 O(log N/2)。使用牛顿法并考虑上限和下限的值将在实践中提高性能。

一旦你找到了最大和最小的索引,它们之间的区别显然就是结果。

对于均匀分布的数字,使用牛顿修正将大大提高运行时间(在连续等距数字序列的情况下,找到最小值和最大值将是 O(1)(两步或三步)),尽管对于任意输入,理论复杂度仍然是 O(log N)。

【讨论】:

    【解决方案2】:

    修改您的二分搜索以查找给定输入的第一次和最后一次出现,然后这两个索引之间的差异就是结果。

    要使用二进制搜索查找第一个和最后一个匹配项,您需要对通常的二进制搜索算法进行一些更改。在二进制搜索中,当找到匹配项时返回该值。但是这里不同于通常的二分搜索,您需要继续搜索,直到找到不匹配的内容。

    有用的链接

    finding last occurence, finding first occurance

    一点更新

    在找到第一个匹配项后,您可以使用该索引作为下一次二进制搜索的起点以找到最后一个。

    【讨论】:

    • 你真的应该详细说明如何在答案本身中进行这些二进制搜索。
    • 考虑这个答案要求您至少阅读不同页面上的 3 个答案才能了解如何执行此操作。然后考虑您可能可以在此答案中用大约 2 个句子总结这两种方法(并提供链接以供其他参考)。为了提供最大的长期价值,后者似乎不费吹灰之力。
    • @StinePike 在 30 秒的时间内给出两个答案。一个获得 7 票,而另一个获得 1 票。
    • @NikunjBanka SO 是出了名的时间敏感。另外,SO 人群不喜欢冗长的答案。较短的答案通常会吸引更多的赞成票,只是因为它可以更快地浏览(无论如何这是我的理论)。如果我们可以指定代码窗口的高度,并且您可以指定它,比如 5 行,我相信您的回答会得到更多的支持。 :)
    • @StinePike - 是的,那将是最坏的情况。我认为您的解决方案最适合对数据性质没有先验知识的大型数据集。
    【解决方案3】:

    实际上有一个比给定解决方案更好的解决方案!它结合了两种不同的二分查找方式。

    首先,您进行二分搜索以获得第一次出现。这是 O(log n)

    现在,从您刚找到的第一个索引开始,您进行另一种二分搜索:您猜测该元素 F 的频率,首先猜测 F=1,然​​后将估计值加倍并检查该元素是否重复。这保证为 O(log F)(其中 F 是频率)。

    这样,算法运行在 O(log N + log F)

    您不必担心数字的分布!

    【讨论】:

      【解决方案4】:

      进行二分搜索以找到第一个出现的位置。进行二分搜索以找到最后一次出现。出现次数等于找到的 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;
          }
      }
      

      【讨论】:

      • 小幅修正。你需要它 ans = -1; 来开始。
      【解决方案5】:

      想到两个解决方案:

      1) 可以进行二分搜索,但要保持它找到第一次出现的不变量。然后进行线性搜索。这将是 Theta(log n + C),其中 C 是计数。

      Jon Bentley 的Programming Pearls 有一篇很好的文章,他提到寻找第一次出现实际上比寻找任何出现都更有效。

      2) 您还可以进行两次二进制搜索,一次用于第一次出现,一次用于最后一次出现,并获取索引的差异。这将是 Theta(log n)。


      您可以根据 C 的期望值来决定应用哪种解决方案。如果 C = o(log n)(是小 o),那么寻找第一次出现并进行线性搜索会更好。否则进行两次二分查找。

      如果您不知道 C 的期望值,那么您最好使用解决方案 2。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-06-05
        • 2020-11-22
        • 1970-01-01
        • 2013-11-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多