我们可以使用二进制搜索来查找排序数组中最常出现的整数吗?

时间:2014-09-07 15:26:42

标签: arrays performance algorithm sorting frequency

问题

给定排序的整数数组,找到最常出现的整数。如果有多个满足此条件的整数,则返回其中任何一个。

我的基本解决方案:

扫描数组并跟踪您看到每个整数的次数。由于它已经排序,你知道一旦你看到一个不同的整数,你就得到了前一个整数的频率。跟踪哪个整数具有最高频率。

这是O(N)时间,O(1)空间解。

我想知道是否有更高效的算法使用某种形式的二进制搜索。它仍然是O(N)时间,但对于普通情况应该更快。

4 个答案:

答案 0 :(得分:2)

渐近(很明智),你不能使用二分搜索来改善最坏的情况,原因是我上面的答案已经提出。但是,以下是一些可能会或可能不会帮助您实践的想法。

对于每个整数,二进制搜索其最后一次出现。一旦找到它,就会知道它出现在阵列中的次数,并可以相应地更新您的计数。然后,从找到的位置继续搜索。

如果只有少数元素可以重复很多次,这是有利的,例如:

1 1 1 1 1 2 2 2 2 3 3 3 3 3 3 3 3 3 3

因为您只会进行3次二进制搜索。但是,如果您有许多不同的元素:

1 2 3 4 5 6

然后你会进行O(n)二进制搜索,导致O(n log n)复杂性,更糟糕。

这为您提供了比最初算法更好的最佳情况和更糟糕的情况。

我们可以做得更好吗?我们可以通过查找位置i上最后一次出现的数字来改善最坏情况:看2i,然后看4i等等,只要这些位置的值是相同。如果不是,请查看(i + 2i) / 2等。

例如,考虑数组:

i
1 2 3 4 5 6 7 ...
1 1 1 1 1 2 2 2 2 3 3 3 3 3 3 3 3 3 3

我们查看2i = 2,它具有相同的值。我们查看4i = 4,相同的值。我们看8i = 8,不同的价值。我们回溯到(4 + 8) / 2 = 6。不同的价值。回溯到(4 + 6) / 2 = 5。价值相同。试试(5 + 6) / 2 = 5,相同的值。我们不再搜索,因为我们的窗口宽度为1,所以我们已经完成了。从位置6继续搜索。

这应该可以改善最佳情况,同时尽可能快地保持最坏情况。

渐近地,没有任何改进。为了确定它在实践中是否真的能够更好地运行,你必须对它进行测试。

答案 1 :(得分:1)

二元搜索,可以消除剩余候选人的一半,可能无法正常工作。您可以使用一些技术来避免读取数组中的每个元素。除非您的阵列太长或者您正在解决好奇心问题,否则天真(线性扫描)解决方案可能已经足够好了。

这就是为什么我认为二进制搜索不起作用:从一个数组开始:给定中间项的值,你没有足够的信息来消除搜索的下半部分或上半部分。

但是,我们可以多次扫描数组,每次检查两次元素。当我们找到两个相同的元素时,最后通过一个。如果没有重复其他元素,您发现最长的元素运行(甚至不知道该元素在排序列表中有多少)。  否则,研究两个(或更多)更长的序列以确定哪个序列最长。

考虑一个排序列表。

Index 0 1 2 3 4 5 6 7 8 9 a b c d e f
List  1 2 3 3 3 3 3 3 3 4 5 5 6 6 6 7
Pass1 1 . . . . . . 3 . . . . . . . 7
Pass2 1 . . 3 . . . 3 . . . 5 . . . 7
Pass3 1 2 . 3 . x . 3 . 4 . 5 . 6 . 7

在第3遍之后,我们知道3的运行必须至少为5,而任何其他数字的最长运行最多为3.因此,3是列表中最常出现的数字。

使用正确的数据结构和算法(使用二叉树样式索引),您可以避免多次读取值。您也可以避免读取3(在第3遍中标记为x),因为您已经知道它的值。

此解决方案的运行时间O(n/k)对于具有n个元素和最长k个元素的列表的O(n)降级为k=1。对于小k,由于更简单的逻辑,数据结构和更高的RAM缓存命中率,天真的解决方案将表现更好。

如果您需要确定最常见数字的频率,David需要O((n/k) log k)才能使用二进制搜索找到最长数字运行的第一个和最后一个位置{{1大小为n/k的小组。

答案 2 :(得分:0)

最坏的情况不能比O(n)时间更好。考虑每个元素存在一次的情况,除了一个存在两次的元素。为了找到该元素,您需要查看数组中的每个元素,直到找到它为止。这是因为知道任何数组元素的值不会提供有关重复元素位置的任何信息,直到找到它为止。这与二进制搜索形成对比,在二进制搜索中,数组元素的值允许您排除许多其他元素。

答案 3 :(得分:0)

不,在最坏的情况下,我们必须扫描至少n - 2个元素,但请参阅 下面是一个利用许多重复输入的算法。

考虑一个对手,对于前n - 3个不同的探测器来说 n元素数组,返回m表示索引m处的值。现在的算法 知道数组看起来像

1 2 3 ... i-1 ??? i+1 ... j-1 ??? j+1 ... k-1 ??? k+1 ... n-2 n-1 n.

根据???的内容,唯一正确的答案可能是j-1j+1,因此算法尚未完成。

这个例子涉及一个重复很少的数组。在 事实上,我们可以设计一种算法,如果是最常见的元素 在n中出现k次,使用O((n / k)log k)探针进入数组。对于 j从ceil(log2(n)) - 1下降到0,检查由子组成的子组 每个(2 ** j)元素。如果我们发现重复,请停止。到目前为止的费用 是O(n / k)。现在,对于子阵列中的每个元素,使用二进制搜索 找到它的范围(O(n / k)搜索大小为O(k)的子阵列,总数 O((n / k)log k))。

可以证明,所有算法都具有Omega((n / k)log的最坏情况 k),在最坏的情况下使这个最佳,直到恒定因子。