这是我的一个面试问题。给定N个元素的数组,其中元素恰好N / 2 次,其余N / 2个元素唯一。您如何找到运行时间更长的元素?
请记住元素未排序,您可以假设N是偶数。例如,
input array [] = { 10, 2, 3, 10, 1, 4, 10, 5, 10, 10 }
所以这里10次出现5次,即N / 2.
我知道O(n)运行时的解决方案。但仍期待通过O(log n)了解更好的解决方案。
答案 0 :(得分:24)
如果您准备好接受很小的错误概率,那么有一个恒定的时间解决方案。从数组中随机抽取两个值,如果它们相同,则找到您要查找的值。在每一步,你有0.75的概率未完成。并且因为对于每个epsilon,存在一个n,使得(3/4)^ n <1。 eps,如果我们没有找到匹配的对,我们最多可以采样n次并返回错误。
还要注意的是,如果我们在找到一对之前继续采样,那么预期的运行时间是恒定的,但最坏情况下的运行时间不受限制。
答案 1 :(得分:19)
这是我试图证明为什么在少于O(n)数组访问中不能做到这一点(对于最坏的情况,这肯定是本例中唯一有趣的情况):
假设存在最坏情况的log(n)算法。该算法最多以log(n)次访问该数组。由于它不能假设哪些元素在哪里,让我选择它看到的哪个log(n)元素。我会选择给它第一个log(n)独特元素。它还没有找到重复,并且仍然存在n / 2 - log(n)个独特元素供我根据需要提供它。实际上,在读取n / 2个元素之前,我不能强迫它输入重复的数字。因此这种算法不存在。
从纯粹直观的角度来看,这似乎是不可能的。 Log(40亿)是32个。所以有一个40亿个数组的数组,其中20亿个是唯一的,没有特别的顺序,有一种方法可以通过检查32个元素找到重复的元素吗?
答案 2 :(得分:16)
我认为你只需要解析数组,保持两个元素的积压。由于N / 2相等且其余部分保证不同,所以在数组中必须有一个位置
a[i] == a[i-1] OR a[i] == a[i-2]
迭代一次通过你的数组,你有大约2 * N的复杂度,它应该在O(N)内。
这个答案有点类似于Ganesh M和Dougie的答案,但我觉得有点简单。
答案 3 :(得分:10)
您不能在次线性时间内执行此操作,因为您需要读取数组。要以对数时间处理一百万条记录的数组,只需要读取~20(log2)元素 - 显然是不可能的。毕竟如果你假设发现的第一个重复重复N / 2次它仍然是O(n),因为你可能需要查看500,001个元素来找到重复。
如果假设整数是非负的,则可以在O(n)中执行此操作。它就像这样(伪Java):
int repeatedNumber = -1; // sentinel value
int count = 0;
BitSet bits = new BigSet(); // this bitset needs to have 2^31 bits, roughly 2.1 billion
boolean duplicate = false;
for (int i : elements) {
if (bits[i].isSet()) {
if (repeatedNumber == -1) {
repeatedNumber = i;
count = 1;
} else if (i == repeatedNumber) {
count++;
} else {
System.out.println("Array has more than one repeated element");
duplicate = true;
break;
}
} else {
bits[i].set();
}
}
if (!duplicate && repeatedNumber != -1 && count == elements.length/2) {
System.out.println(repeatedNumber + " occurred " + count + " times. The rest of the elements are unique");
} else {
System.out.println("Not true");
}
类似的方法用于对O(n)(基数排序)中的唯一整数数组进行排序。
答案 4 :(得分:10)
对于最坏情况确定性行为,O(N)是正确的(我在前面的答案中已经看到过多个证据)。
然而,现代算法理论并不仅仅关注最坏情况的行为(这就是为什么除了big-O之外还有很多其他的大事,即使懒惰的程序员匆忙经常使用big-O,即使它们是什么考虑到更接近big-theta或big-omega ;-),也不仅仅是决定论(用米勒 - 拉宾素性测试......)。
K
这将是一个非常糟糕的面试问题。类似的不那么糟糕的问题经常被提出,经常被错误回答,和经常被不成功的候选人误解。例如,一个典型的问题可能是,给定N个项目的数组,不知道是否存在多数项目,以确定是否存在一个,以及是否存在一个项目,在O(N)中时间和 O(1)辅助空间(因此您不能只设置哈希表或其他东西来计算不同值的出现次数)。 “摩尔的投票方法”是一个很好的解决方案(可能是最好的解决方案) 值得面试的问题。
另一个有趣的变化:如果你有10**18
64位数字(总体上是8TB的数据,比如在bigtable或克隆上),以及你想要的多台机器,每个都有大约4GB在一个非常快的局域网上的RAM,比如一个比GB以太网好得多的内存 - 你如何在那些条件下对问题进行分类?如果你必须使用mapreduce / hadoop怎么办?如果您可以自由地为这一个问题设计自己的专用框架,那么您可以获得比使用mapreduce更好的性能吗?在包络后估计的粒度上有多好?我知道没有已发布的THIS变种算法,所以如果你想通过高度分散的万亿级计算方法检查候选人的一般设施,这可能是一个很好的测试......
答案 5 :(得分:6)
我的回答是,
运行时 - O(N)
答案 6 :(得分:5)
设S是包含N个元素的集合。它是两组的并集:p,包含重复N / 2次的符号α,q包含N / 2个唯一符号ω 1 ..ω n / 2 < /子>。 S =p∪q。
假设有一种算法可以在log(n)比较中检测你的重复数字,在最坏的情况下对于所有N&gt; 2.在最坏的情况下意味着不存在任何子集r⊂S,使得| r | = log 2 N其中α∉r。
然而,因为S =p∪q,所以有| p | S. | p |中的许多元素≠α = N / 2,所以∀N / 2使得N /2≥log 2 N,必须存在至少一个r rS,使得| r | = log 2 N和α∉r。任何N≥3都是这种情况。这与上述假设相矛盾,因此不能有任何这样的算法。
QED。
答案 7 :(得分:3)
要比O(n)少,你就不必阅读所有数字 如果您知道有一个值可以满足关系,那么您可以只对一个小的子集进行采样,只显示一个数字足以满足关系。您必须假设值合理均匀分布
编辑。你必须阅读n / 2以证明存在这样的数字,但是如果你知道存在一个数字并且只想找到它 - 你可以读取sqrt(n)样本
答案 8 :(得分:3)
很容易看到没有O(log n)算法存在。显然,您必须查看数组元素以确定哪个是重复元素,但无论您选择查看元素的顺序如何,您查看的第一层(n / 2)元素都可能是唯一的。你可能只是不走运。如果发生这种情况,你将无法知道哪个是重复元素。由于在每次运行中使用少于floor(n / 2)数组引用或更少的算法都不起作用,因此肯定没有子线性算法。
答案 9 :(得分:3)
答案很简单..并且可以在最坏情况下(n / 2 + 1)比较实现
比较成对的第一个(n-2)个数字,即比较nos。在0和1,然后是2和3等等...总n / 2 -1比较。 如果我们在上述任何比较中找到相同的数字..我们有重复的数字......其他:
取最后两个剩余数字中的任意一个(比如我最后一个数字),然后将它与第二对数字中的数字进行比较..如果匹配发生......最后一个没有。是重复的,否则最后一个是重复的...在所有2个比较中。
总比较= n / 2 - 1 + 2 = n / 2 + 1(最差情况) 我不认为有任何O(log n)方法来实现这个
答案 10 :(得分:1)
如果我正确地理解了这个问题:我们所知道的数据就是它的长度并且它有(N / 2)+1个唯一元素,其中1个元素重复N / 2次(没有特定的顺序)。
我认为这对解决方案有一个O(N)的硬限制,因为你无法确定(对于通用数组)你找到了这个数字而没有找到至少2个相同的数字。我不认为存在搜索无序数组,可以检测到O(logN)中的重复(如果我错了,请纠正我)。在最坏的情况下,您总是需要读取至少N / 2 + 1个元素。
答案 11 :(得分:1)
将我的解决方案从评论重新发布到Ganesh的版本,以便我可以格式化它:
for (i=0; i<N-2; i+=3) {
if a[i] == a[1+1] || a[i] == a[i+2] return a[i];
if a[i+1] == a[i+2] return a[i+1];
}
return a[N-1]; // for very small N
1次迭代后获胜的可能性:50%
2次迭代后获胜的可能性:75%
等
最坏情况,O(n)时间O(1)空间。
请注意,在N / 4次迭代之后,你已经用完了所有N / 2个唯一数字,所以如果指定的话,这个循环将永远不会迭代超过3/4的数组。
答案 12 :(得分:1)
假设你有一个像这样的python算法:
import math
import random
def find_duplicate(arr, gap):
cost, reps = 0, 0
while True:
indexes = sorted((random.randint(0,len(arr)-i-1) for i in xrange(gap)), reverse=True)
selection = [arr.pop(i) for i in indexes]
selection_set = set(selection)
cost += len(selection)
reps += 1
if len(selection) > len(selection_set):
return cost, reps
我们的想法是 arr 是您的一组值,而 gap 是该大小的日志基数-2。每次选择 gap 元素并查看是否存在重复值。如果是这样,返回您的成本(检查的元素数)和迭代次数(每次迭代检查log2(大小)元素的位置)。否则,请查看另一个 gap -sized set。
对该算法进行基准测试的问题在于,假设存在大量数据,每次通过循环创建数据和更改数据都是昂贵的。 (最初,我做了1 000 000个元素,迭代次数为10 000 000次。)
所以让我们减少到一个同等的问题。数据作为n / 2个唯一元素和n / 2个重复元素传递。该算法选择log2(n)元素的随机索引并检查重复。现在我们甚至不必创建数据并删除检查的元素:我们可以检查在中途点是否有两个或更多索引。选择 gap 索引,在中途点检查2个或更多:如果找到则返回,否则重复。
import math
import random
def find_duplicate(total, half, gap):
cost, reps = 0, 0
while True:
indexes = [random.randint(0,total-i-1) for i in range(gap)]
cost += gap
reps += 1
above_half = [i for i in indexes if i >= half]
if len(above_half) >= 2:
return cost, reps
else:
total -= len(indexes)
half -= (len(indexes) - len(above_half))
现在像这样驱动代码:
if __name__ == '__main__':
import sys
import collections
import datetime
for total in [2**i for i in range(5, 21)]:
half = total // 2
gap = int(math.ceil(math.log10(total) / math.log10(2)))
d = collections.defaultdict(int)
total_cost, total_reps = 0, 1000*1000*10
s = datetime.datetime.now()
for _ in xrange(total_reps):
cost, reps = find_duplicate(total, half, gap)
d[reps] += 1
total_cost += cost
e = datetime.datetime.now()
print "Elapsed: ", (e - s)
print "%d elements" % total
print "block size %d (log of # elements)" % gap
for k in sorted(d.keys()):
print k, d[k]
average_cost = float(total_cost) / float(total_reps)
average_logs = average_cost / gap
print "Total cost: ", total_cost
print "Average cost in accesses: %f" % average_cost
print "Average cost in logs: %f" % average_logs
print
如果您尝试此测试,您会发现算法必须执行多项选择的次数会随着数据中元素的数量而下降。也就是说,日志中的平均成本渐近接近1 。
elements accesses log-accesses
32 6.362279 1.272456
64 6.858437 1.143073
128 7.524225 1.074889
256 8.317139 1.039642
512 9.189112 1.021012
1024 10.112867 1.011287
2048 11.066819 1.006075
4096 12.038827 1.003236
8192 13.022343 1.001719
16384 14.013163 1.000940
32768 15.007320 1.000488
65536 16.004213 1.000263
131072 17.002441 1.000144
262144 18.001348 1.000075
524288 19.000775 1.000041
1048576 20.000428 1.000021
现在这是理想算法的一个参数,在平均情况下是log2(n) ?也许。在最坏的情况下肯定不是这样。
此外,您不必一次选择log2(n)元素。您可以选择2并检查是否相等(但在退化的情况下,您根本找不到重复项),或者检查任何其他更大的数字以进行复制。此时,选择元素和检查重复的所有算法都是相同的,只会根据它们选择的数量和它们识别重复的方式而有所不同。
答案 13 :(得分:0)
与https://stackoverflow.com/a/1191881/199556解释类似。
让我们比较3个元素(3个比较操作),在更糟糕的情况下,“相同”元素将出现一次。 因此,我们将尾部减少3并将“相同”元素的数量减少一个。
在最后一步(k次迭代后),我们的尾部将包含(n / 2) - k个“相同”元素。让我们比较尾巴的长度。
一方面它将是n-3k 另一方面(n / 2) - k + 1.可能存在最后的未命名元素。
n-3k =(n / 2) - k + 1
k = 1/4 *(n-2)
经过k次迭代后,我们肯定会得到结果。
比较次数3/4 *(n-2)
答案 14 :(得分:0)
算法RepeatedElement(a, n)
while (true) do
{
i=Random() mod n+1; j=Random() mod n+1;
// i and j are random numbers in the range [1,n]
if ((i ≠ j) and a[i]=a[j])) then return;
}
答案 15 :(得分:0)
Ruby中的Don Johe's answer:
#!/usr/bin/ruby1.8
def find_repeated_number(a)
return nil unless a.size >= 3
(0..a.size - 3).each do |i|
[
[0, 1],
[0, 2],
[1, 2],
].each do |j1, j2|
return a[i + j1] if a[i + j1] == a[i + j2]
end
end
end
p find_repeated_number([1, 1, 2]) # => 1
p find_repeated_number([2, 3, 2]) # => 1
p find_repeated_number([4, 3, 3]) # => 1
O(n)的
答案 16 :(得分:0)
如果你被告知你正在寻找的元素是非唯一元素,那么最快的方法就是沿着数组迭代,直到找到两个相同的元素,然后返回该元素并停止查找。最多你必须搜索一半的数组。
我认为这是O(n)因此我认为它并没有真正帮助。
看起来太简单了,所以我觉得我不能正确理解这个问题。
答案 17 :(得分:-1)
这是一个糟糕的面试问题。
主要是因为第一个。你在找什么?候选人应该提出你不知道的这个O(log n)解决方案吗?如果你不得不问StackOverflow,那么你可以合理地期望候选人能够在面试中提出这个问题吗?
答案 18 :(得分:-1)
与上述答案相反,有一个解决方案,其中包含最坏情况行为,O(log n) RUN TIME 。 问题不是找到O(log N)比较最坏情况(这是不可能的)的解决方案,而是要做O(log N)时间。
如果你可以并行进行N次比较,解决方案就是一个微不足道的分而治之。 在现实世界中不太实用,但这是一个面试问题,而不是现实世界的问题。
更新:我认为您可以使用O(N)处理器在固定时间内完成此操作
答案 19 :(得分:-1)
首先,它已经过了我的睡觉时间,我应该知道比在公共场合发布代码更好,而不是先试试,yada,yada。我希望我得到的批评至少会具有教育意义。 : - )
我认为问题可以重申为:“查找多次出现的数字。”
在绝对最坏的情况下,在找到非唯一数字的第二个实例之前,我们需要迭代一半以上的列表(1 + N / 2)。
最坏情况示例:array [] = {1,2,3,4,5,10,10,10,10,10}
在 average 上,我们只需要迭代3或4个元素,因为一半元素将包含非唯一数字,即大致每隔一个数字。
完美均匀的分布示例:
换句话说,即使N = 100万,你仍然只需要搜索;平均而言,在您发现重复之前的前3或4个元素。
对于不随N增加的固定/常量运行时的大O符号是什么?
代码:
int foundAt = -1;
for (int i=0; (i<N) && (foundAt==-1); i++)
{
for (int j=i+1; j<N; j++)
{
if (array[i] == array[j])
{
foundAt = i;
break;
}
}
}
int uniqueNumber = array[foundAt];