O(nlogn)算法-在二进制字符串中找到三个均匀间隔的算法


173

昨天我在算法测试中遇到了这个问题,但找不到答案。这绝对让我发疯,因为它价值40分。我认为大多数班级都无法正确解决该问题,因为在过去的24小时内我没有提出解决方案。

给定任意一个长度为n的二进制字符串,请在该字符串中找到三个均等的字符串(如果存在)。编写一个算法,可以在O(n * log(n))时间内解决此问题。

因此,这样的字符串具有三个“等间距”的字符串:11100000,0100100100

编辑:这是一个随机数,因此它应该可以处理任何数字。我提供的示例旨在说明“均匀分布”的属性。因此1001011是有效数字。其中1、4和7是等距排列的。


4
是否可能是:10011010000?它具有三个均匀间隔开的1(第一,第二,第四),但也有其他1。
安娜,

5
罗伯特,您需要让您的教授为您提供答案并将其发布在此处。这个问题使我无所适从。我可以弄清楚如何在n ^ 2中做到这一点,但不能解决n * log(n)。
James McMahon

3
嗯,我也花了很长时间试图弄清楚这一点,还没有找到一个好的答案。也许您误解了这个问题?例如,如果问题提出,找到一个以O(n log n)运行的算法,该算法确定间距为k的均匀间隔序列的位置(以更大的顺序),这可以使用快速傅立叶变换轻松完成。
ldog

2
如果您的教授提供了解决方案,请将其作为答案。
ldog

5
考虑到克劳斯·罗斯(Klaus Roth)因其他原因获得1958年菲尔兹奖的事实,证明每一个密度d> 0都有一个自然数N,使得{1,...,N}的每个子集至少具有d * N个元素包含长度为3的算术级数,对于现在为止还没有人找到令人信服的算法,我并不感到惊讶。另请参见en.wikipedia.org/wiki/Szemer%C3%A9di%27s_theorem
jp

Answers:


128

最后!跟进sdcvvc答案中的线索,我们得到了:问题的O(n log n)算法!了解之后,它也很简单。那些认为FFT是正确的人。

问题是:给我们一个S长度为n的二进制字符串,我们想在其中找到三个均匀间隔的1。例如,S可能是110110010,其中n = 9。它在位置2、5和8处平均间隔为1。

  1. S从左到右扫描,并列出L位置1。对于S=110110010上面的列表,我们有L = [1、2、4、5、8]。这一步是O(n)。现在的问题是要找到一个长度为3的算术级数L,即找到不同的a,b,CL,使得BA = CB,或者等效A + C = 2b中。对于上面的示例,我们要查找进度(2、5、8)。

  2. 对于中的每个k,用项x k构成多项式 。对于上面的示例,我们使多项式p(x)=(x + x 2 + x 4 + x 5 + x 8。此步骤为O(n)。pL

  3. 使用快速傅立叶变换找到多项式q= p 2。对于上面的示例,我们得到多项式q(x)= x 16 + 2x 13 + 2x 12 + 3x 10 + 4x 9 + x 8 + 2x 7 + 4x 6 + 2x 5 + x 4 + 2x 3 + x 2此步骤为O(n log n)。

  4. 忽略与x 2k对应的所有项(对于in中的某些kL。对于上面的示例,我们得到的术语X 16,3× 10,X 8,X 4,X 2。如果您选择完全这样做,则此步骤为O(n)。

这里的关键点:任何系数X 2BbL精确的对数(A,C)L,使得A + C = 2b中。[CLRS,例如 [30.1-7]一对这样的对总是(b,b)(因此系数至少为1),但是如果存在其他对(a,c),那么该系数至少为(a,c)的3。 )(c,a)。对于上面的示例,由于AP(2,5,8),我们的x 10系数正好为3。(这些系数 x 2b由于上述原因,将始终为奇数。q中的所有其他系数将始终为偶数。)

因此,该算法将查看这些项x 2b的系数,并查看它们中的任何一个是否大于1。如果没有,则没有均匀间隔的1s。如果有一个bL为其中的系数X 2b的是大于1,则我们知道有一些对(A,C) -比其它(B,B)对于其中- A + C = 2b中。要查找实际的对,我们只是尝试每一个L(对应ç2B-A ),看看是否有一个1在位置2B-AS。此步骤为O(n)。

就是这样,伙计们。


有人可能会问:我们需要使用FFT吗?很多答案,如测试的flybywire的,和RSP的,建议的做法是检查每对1S的,并认为如果有一个1在“第三”的位置,在O可能会工作(N log n)的基础上,直觉如果1太多,我们将很容易找到三元组;如果1太少,则检查所有对都花费很少的时间。不幸的是,虽然这种直觉是正确的,简单的方法优于为O(n 2),它是不是好显著。就像sdcvvc的答案一样,我们可以采用长度为n = 3 k的字符串的“类似Cantor的集合”,在三元表示中仅包含0和2(不含1)的位置处为1。这样的字符串中有2 k = n (log 2)/(log 3) ≈n 0.63个,并且没有均匀间隔的1s,因此检查所有对将是其中1s的平方的数量级:4 ķ ≈ñ 1.26不幸比渐近大得多(N log n)的。实际上,最坏的情况甚至更糟:1953年,Leo Moser 构造(有效)了其中具有n 1-c /√(log n) 1s但没有均匀间隔1的弦,这意味着在这样的弦上,简单方法将采取Θ(N 2-2c /√(log n)-只有一个很小的令人惊讶的是,它比Θ(n 2好一点!


长度为n的字符串中最大1的个数,没有3个均匀间隔的(我们在上面看到的,至少是简单的Cantor型构造的n为0.63,而至少n 1-c /√(log n)为Moser的结构)-这是OEIS A003002。也可以直接从OEIS A065825计算为k,以使A065825(k)≤n <A065825(k + 1)。我写了一个程序来找到这些字符串,结果证明贪婪算法不能给出最长的字符串。例如,对于n = 9,我们可以获得5 1s(110100011),但是对于n,贪婪只给出4(110110000)= 26我们可以得到11 1(11001010001000010110001101),但是贪心只得到8(11011000011011000000000000),对于n = 74我们可以得到22 1(11000010110001000001001011010001000000000000000010001011010000010001101000011),但是贪婪只得到16(11011000011011000000000000011011000011011000000000000000000000000000)。但是,他们确实在相当多的地方达成共识,直到50个(例如38个到50个)。如OEIS参考文献所述,Jaroslaw Wroblewski似乎对此问题感兴趣,并且他在这些非平均集合上维护了一个网站。确切数字最多只能知道194。


27
非常好。令人印象深刻。期望有人在测试中提出这个建议似乎有点困难。
hughdbrown

4
好吧,第1步将问题转化为找到AP很简单。步骤3(多项式可以乘以O(n log n)的时间)只是一个事实。真正的窍门(是使问题变得棘手的问题)是将11011视为具有系数[1,1,0,1,1]等的多项式的想法。这是一个聪明且经常有用的想法,回到欧拉。[有关现代的论述,请参见威尔夫的著作《 generatingfunctionology》,这是现代的论述:math.upenn.edu/~wilf/DownldGF.html ]因此,这取决于学生是否暴露于最近记忆中的生成函数。:-)
ShreevatsaR

2
对不起,我的计算完全错误。它应该是110110010 ^ 2 = 12124214302200100。但是这个想法是正确的。只要注意3的位置
吉列尔莫·菲利普斯

11
非常令人印象深刻。看到这个线程/问题并找到解决方案真的很酷。我开始认为这是不可能的。另外,这位教授是邪恶的。
KingNestor

1
@RexE:如果p的阶数为n-1(具有n个项),则q = p ^ 2的阶数为2n-2(具有2n-1个项)。你是怎么得到n ^ 2的?(此外,使用FFT在O(n log n)时间中将n的两个多项式相乘是一种非常标准的操作;请单击答案中的链接或查看Wikipedia文章。)
ShreevatsaR 2010年

35

您的问题在本文(1999年)中称为AVERAGE :

如果从问题3SUM进行次二次约减,则问题很棘手:给定A由n个整数组成的集合A,A中是否存在元素a,b,c使得a + b + c = 0?尚不清楚AVERAGE是否为3SUM硬性。但是,从AVERAGE到3SUM有一个简单的线性时间减少,我们省略了对它的描述。

维基百科

当整数在[-u ... u]范围内时,可以通过将S表示为位向量并使用FFT进行卷积来在时间O(n + u lg u)中求解3SUM。

这足以解决您的问题:)。

什么是非常重要的是,为O(n log n)的是在零和一的数量方面的复杂性,而不是那些的计数(可以给出一个数组,如[1,5,9,15])。检查集合是否具有算术级数,1的项的条件是困难的,并且根据该论文,截至1999年,尚不知道有比O(n 2)更快的算法,并且推测该算法不存在。每个不考虑这一点的人都在尝试解决一个开放的问题。

其他有趣的信息,大多是无关紧要的:

下限:

一个简单的下限是类Cantor集(数字1..3 ^ n-1的三进制展开数中不包含1)-其密度为n ^(log_3 2)(约0.631)。因此,如果仅检查集合是否太大,然后再检查所有对,就不足以得到O(n log n)。您必须更智能地研究序列。此处引用一个更好的下限-它是n 1-c /(log(n))^(1/2)。这意味着Cantor集不是最佳的。

上限-我的旧算法:

已知对于大n,不包含算术级数的{1,2,...,n}的子集最多具有n /(log n)^(1/20)个元素。关于算术级数中的三元组的论文证明了更多:该集合不能包含n * 2 28 *(log log n / log n)1/2个元素。因此,您可以检查是否已达到该界限,否则,请天真的检查对。这是O(n 2 * log log n / log n)算法,比O(n 2)快。不幸的是,在Springer上有“在三元组上……”,但第一页可用,而Ben Green的说明在此处,第28页,定理24。

顺便说一句,这些论文来自1999年-与我提到的第一篇论文的年份相同,所以这可能就是为什么第一篇论文没有提及该结果的原因。


2
很好的答案,第一个说了关于这个问题的任何权威。因此,类似Cantor的集合具有n ^ 0.63 1s,这意味着在最坏的情况下,“检查所有对1s”算法至少为n ^ 1.26(≫ n log n)。在Szemeredi的论文下界引用(BTW的莫泽纸,他的报价可以在这里找到:books.google.com/books?id=Cvtwu5vVZF4C&pg=PA245)似乎在实际上意味着ñ^(2-O(1)),但我们必须请稍加注意,因为我们有从{1,...,n}得出的数字,但这是序列中n的数字总和
ShreevatsaR

,,“ cantor-like”二进制序列到底包含n ^(log_3 2)1个,而没有三个均匀间隔的1个,到底是什么?
ShreevatsaR

示例:101000101000000000101000101。其长度为3 ^ n,并具有2 ^ n(即n ^ 0.63密度)。如果以二进制形式记下1的位置,则它将为{0,2,20,22,200,202,220,222}。考虑它的另一种可能方法是采用一个序列,并像正常的Cantor集构造一样连续删除“中间”序列:111111111-> 111000111->101000101。它不包含算术级数的原因是:如果x ,y,z形成一个,则y =(x + z)/ 2,并且x和z在某个扩展位置不同。拿最重要的一个。假设x的值为0,z的值为2。那么y的值必须为1。矛盾。
sdcvvc

3
再次,伟大的研究!我跟踪了2008年3SUM论文,该论文涉及CLRS练习。30.1-7,看了看我得到的答案之后,O(n log n)算法实际上非常简单!(仅对一个多项式/生成函数求平方。)我在下面发布了答案。(现在因为没有早点想到就踢自己...简单的解决方案总是引起这种反应:p)
ShreevatsaR

因此,他的考试问题的答案是这样的:“此问题可简化为3-SUM难题,而3-SUM难题没有次二次解,因此该问题无法用O(n logn)解决。 ” 是?
hughdbrown,2009年

8

这不是解决方案,而是与Olexiy的想法类似的思路

我一直在尝试创建最大数量的序列,它们都很有趣,我最多可以找到125位数字,这是尝试插入尽可能多的'1'位找到的前3个数字:

  • 11011000011011000000000000001101100001101100000000000000000000000000000000000000000000000110110000110110000000000000011011000011011
  • 10110100010110100000000000010110100010110100000000000000000000000000000000000000000101101000101101000000000000101101000101101
  • 100110010100110010000000000100110010100110010000000000000000000000000000000000000100100101010011001000000000010011001010011001

请注意,它们都是分形的(鉴于约束条件,这并不奇怪)。向后思考可能有些问题,如果字符串不是具有特征的分形,那么它一定有重复的模式吗?

感谢beta提供了更好的术语来描述这些数字。

更新: las看起来当从足够大的初始字符串开始时,模式会崩溃,例如:10000000000001:

100000000000011
10000000000001101
100000000000011011
10000000000001101100001
100000000000011011000011
10000000000001101100001101
100000000000011011000011010000000001
100000000000011011000011010000000001001
1000000000000110110000110100000000010011
1000000000000110110000110100000000010011001
10000000000001101100001101000000000100110010000000001
10000000000001101100001101000000000100110010000000001000001
1000000000000110110000110100000000010011001000000000100000100000000000001
10000000000001101100001101000000000100110010000000001000001000000000000011
1000000000000110110000110100000000010011001000000000100000100000000000001101
100000000000011011000011010000000001001100100000000010000010000000000000110100001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011001000000000000000000000010010000010000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001001000000000000000000000000000000000000110010000000000000000000000100100000100000011
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001000001000000110000000000001

2
神圣的* @ !!,这些是分数!如果成立,则将上限设置为1,并且小于O(n)。
Beta Beta

分形,这是描述它们的更好的术语。谢谢
z-

有趣的是,这些模式非常类似于Cantor的三元组(en.wikipedia.org/wiki/Cantor_set)。如果是这样的话,那么那个人的比例就必须趋于零...
flybywire

显而易见,最大数目为1且没有三元组的序列与算法最坏情况下的运行时间直接相关吗?可以想象的是,您可能有很多带有1的字符串,但是在其中您只能找到很晚的三元组,因为这些1处于算法检查的较晚位置。
ShreevatsaR

3
我对字符串中的个数与其总大小的比较分析似乎表明,个数和字符串大小之间存在线性关系,这使我相信没有快乐的上限可以让我们说给定字符串的最大数目为log(n)。因此,仅处理一个位置而不是整个字符串本身的解决方案也将是O(n ^ 2)。或者更准确地说是O(n + m ^ 2),其中m是字符串中的个数,n是字符串的大小,m是big-theta(n)。
Welbog

6

我怀疑看起来像O(n ^ 2)的简单方法实际上会产生更好的结果,例如O(n ln(n))。测试时间最长的序列(对于任何给定的n)是不包含三重奏的序列,这对序列中可以存在的1的数目施加了严格的限制。

我提出了一些挥之不去的论点,但是我还没有找到一个整洁的证明。我要在黑暗中刺痛一下:答案是一个非常聪明的主意,教授已经知道了很长时间,以至于看起来很明显,但这对学生来说太难了。(或者,或者您睡过了涵盖该课程的演讲。)


2
大声笑,不,我没有听任何演讲。我与其他几位学生进行了交谈,但没人对如何解决这个问题有一个清晰的主意。多数人写了一些有关分而治之的学士学位,以获得部分荣誉。
罗伯特·帕克,2009年

3

修订日期:2009-10-17 23:00

我已经对此进行了大量运算(例如2000万个字符串),我现在认为该算法不是O(n logn)。尽管如此,它还是一个很酷的实现,并且包含许多使其真正运行迅速的优化。它在25秒内评估24位或更少位二进制字符串的所有排列。

我已经更新了代码,以包括0 <= L < M < U <= X-1今天早些时候的观察。


原版的

从概念上讲,这与我回答的另一个问题类似。该代码还查看了序列中的三个值,并确定三元组是否满足条件。这是从中改编的C#代码:

using System;
using System.Collections.Generic;

namespace StackOverflow1560523
{
    class Program
    {
        public struct Pair<T>
        {
            public T Low, High;
        }
        static bool FindCandidate(int candidate, 
            List<int> arr, 
            List<int> pool, 
            Pair<int> pair, 
            ref int iterations)
        {
            int lower = pair.Low, upper = pair.High;
            while ((lower >= 0) && (upper < pool.Count))
            {
                int lowRange = candidate - arr[pool[lower]];
                int highRange = arr[pool[upper]] - candidate;
                iterations++;
                if (lowRange < highRange)
                    lower -= 1;
                else if (lowRange > highRange)
                    upper += 1;
                else
                    return true;
            }
            return false;
        }
        static List<int> BuildOnesArray(string s)
        {
            List<int> arr = new List<int>();
            for (int i = 0; i < s.Length; i++)
                if (s[i] == '1')
                    arr.Add(i);
            return arr;
        }
        static void BuildIndexes(List<int> arr, 
            ref List<int> even, ref List<int> odd, 
            ref List<Pair<int>> evenIndex, ref List<Pair<int>> oddIndex)
        {
            for (int i = 0; i < arr.Count; i++)
            {
                bool isEven = (arr[i] & 1) == 0;
                if (isEven)
                {
                    evenIndex.Add(new Pair<int> {Low=even.Count-1, High=even.Count+1});
                    oddIndex.Add(new Pair<int> {Low=odd.Count-1, High=odd.Count});
                    even.Add(i);
                }
                else
                {
                    oddIndex.Add(new Pair<int> {Low=odd.Count-1, High=odd.Count+1});
                    evenIndex.Add(new Pair<int> {Low=even.Count-1, High=even.Count});
                    odd.Add(i);
                }
            }
        }

        static int FindSpacedOnes(string s)
        {
            // List of indexes of 1s in the string
            List<int> arr = BuildOnesArray(s);
            //if (s.Length < 3)
            //    return 0;

            //  List of indexes to odd indexes in arr
            List<int> odd = new List<int>(), even = new List<int>();

            //  evenIndex has indexes into arr to bracket even numbers
            //  oddIndex has indexes into arr to bracket odd numbers
            List<Pair<int>> evenIndex = new List<Pair<int>>(), 
                oddIndex = new List<Pair<int>>(); 
            BuildIndexes(arr, 
                ref even, ref odd, 
                ref evenIndex, ref oddIndex);

            int iterations = 0;
            for (int i = 1; i < arr.Count-1; i++)
            {
                int target = arr[i];
                bool found = FindCandidate(target, arr, odd, oddIndex[i], ref iterations) || 
                    FindCandidate(target, arr, even, evenIndex[i], ref iterations);
                if (found)
                    return iterations;
            }
            return iterations;
        }
        static IEnumerable<string> PowerSet(int n)
        {
            for (long i = (1L << (n-1)); i < (1L << n); i++)
            {
                yield return Convert.ToString(i, 2).PadLeft(n, '0');
            }
        }
        static void Main(string[] args)
        {
            for (int i = 5; i < 64; i++)
            {
                int c = 0;
                string hardest_string = "";
                foreach (string s in PowerSet(i))
                {
                    int cost = find_spaced_ones(s);
                    if (cost > c)
                    {
                        hardest_string = s;
                        c = cost;
                        Console.Write("{0} {1} {2}\r", i, c, hardest_string);
                    }
                }
                Console.WriteLine("{0} {1} {2}", i, c, hardest_string);
            }
        }
    }
}

主要区别在于:

  1. 解决方案的详尽搜索
    该代码生成一组强大的数据,以找到最难解决的输入来解决该算法。
  2. 所有解决方案与最难解决
    的问题上一个问题的代码使用python生成器生成了所有解决方案。该代码仅显示每个模式长度最难的代码。
  3. 评分算法
    此代码检查从中间元素到其左手边和右手边的距离。python代码测试总和是大于还是小于0。
  4. 融合候选人
    当前代码从中间到边缘开始寻找候选者。上一个问题中的代码从边缘到中间。最后的更改大大提高了性能。
  5. 偶数和奇数池的使用
    根据本文结尾处的观察,该代码搜索偶数对的奇数对以查找L和U,并使M保持固定。这样可以通过预先计算信息来减少搜索次数。因此,该代码在FindCandidate的主循环中使用了两个间接级别,并且需要为每个中间元素两次调用FindCandidate:一次是偶数,一次是奇数。

通常的想法是处理索引,而不是数据的原始表示。通过计算出现1的数组,算法可以按与数据中1的数量成正比的时间运行,而不是按与数据长度成比例的时间运行。这是标准的转换:创建一个数据结构,可以在保持问题等同的同时加快操作速度。

结果已过期:已删除。


编辑:2009-10-16 18:48

在yx的数据上(在其他响应中有一定的可信度可作为要计算的硬数据的代表),我得到了这些结果……我删除了这些结果。他们已经过时了。

我要指出的是,对于我的算法而言,这些数据并不是最难的,所以我认为yx的分形最难求解的假设是错误的。我预计,特定算法的最坏情况将取决于算法本身,并且在不同算法之间可能不会保持一致。


编辑:2009-10-17 13:30

关于此的进一步观察。

首先,将0和1的字符串转换为1的每个位置的索引数组。假设数组A的长度为X。那么目标是找到

0 <= L < M < U <= X-1

这样

A[M] - A[L] = A[U] - A[M]

要么

2*A[M] = A[L] + A[U]

由于A [L]和A [U]的总和为偶数,所以它们不能为(偶数​​,奇数)或(奇数,偶数)。可以通过将A []分为奇数和偶数池并依次在奇数和偶数候选池中的A [M]上搜索匹配项来改善对匹配的搜索。

但是,我认为这更多是性能优化而不是算法改进。比较次数应该减少,但是算法的顺序应该相同。


编辑2009-10-18 00:45

对我来说,还有另一个优化方法,与将候选对象分为偶数和奇数一样。由于这三个索引必须加3的倍数(a,a + x,a + 2x-mod 3为0,与a和x无关),因此可以将L,M和U分成其mod 3值:

M  L  U
0  0  0
   1  2
   2  1
1  0  2
   1  1
   2  0
2  0  1
   1  0
   2  2

实际上,您可以将其与偶数/奇数观测值相结合,并将其分为mod 6值:

M  L  U
0  0  0
   1  5
   2  4
   3  3
   4  2
   5  1

等等。这将提供进一步的性能优化,但不能提供算法上的加速。


2

尚无法提出解决方案:(,但是有一些想法。

如果我们从一个逆向问题开始,该怎么办:构造一个最大数目为1且没有任何均匀间隔的三重奏的序列。如果可以证明最大1的个数为o(n),则可以通过仅对1的列表进行迭代来改善估计。


好吧,1的数目肯定在O(n)的上方。它不能是O(n ** 2),对-1的数量增长快于数据增长?重要的问题是上限是否低于该上限。
hughdbrown

我用的是小o,而不是大o
Olexiy

2

这可能有帮助。

此问题减少到以下情况:

给定一个正整数序列,请找到一个连续的子序列,该子序列被划分为一个前缀和一个后缀,以使该子序列的前缀之和等于该子序列的后缀之和。

例如,给定一个序列[ 3, 5, 1, 3, 6, 5, 2, 2, 3, 5, 6, 4 ],我们将找到一个子序列,其子序列为,[ 3, 6, 5, 2, 2]前缀为[ 3, 6 ],前缀总和为9,后缀为[ 5, 2, 2 ],后缀总和为9

减少如下:

给定一系列零和一,并从最左边的一个开始,继续向右移动。每次遇到另一个移动时,记录自遇到上一个移动以来的移动次数,并将该数字附加到结果序列中。

例如,给定一个序列[ 0, 1, 1, 0, 0, 1, 0, 0, 0, 1 0 ],我们会发现的减少[ 1, 3, 4]。从本次减持,我们计算的连续子序列[ 1, 3, 4],前缀[ 1, 3]用的总和4,而后缀[ 4 ]以总和4

该减少量可以在中计算O(n)

不幸的是,我不确定从这里出发。


1
这是一种更紧凑的表示法,但对时间的复杂性没有帮助。在所有出现的“ 1”(即O(n ^ 2))中,“前缀”分区的集合对于所有对搜索都是同构的。
p00ya

显然有一些算法可以处理连续的子序列和。不幸的是,他们似乎都在寻找在O(n)中具有最大和的连续子序列。
yfeldblum

@ p00ya这是不正确的。使用这种算法,时间复杂度取决于错误的数目的上限,在Cantor生成的字符串上假设这是((3/2)^(log(n)/ log(3))),空间复杂度变为但是时间复杂度乘以n。检查我的第二个答案。(不是负面的):D
Luka Rahne 09年

@ralu:这是基于您的假设,即Cantor生成的字符串是最坏的情况,这是错误的。为了记录,对的数量肯定是O(n ^ 2); 但是我想我真的是在暗示这是big-Omega(n ^ 2),鉴于这些结果,这是不正确的(尤其请参见NrootN链接),这表明big-Omega(n ^(2 / 1.52 ))通过证明或big-Omega(n ^(4/3))通过猜想。
p00ya

1

对于简单的问题类型(即,您搜索三个“ 1”之间仅存在(即零个或多个)“ 0”),这非常简单:您可以将序列拆分为每个“ 1”,然后寻找两个相邻的子序列长度相同(第二个子序列当然不是最后一个)。显然,这可以在O(n)时间内完成。

对于更复杂的版本(即,您搜索索引ig > 0使得g等于0 s[i]==s[i+g]==s[i+2*g]=="1"),我不确定是否存在O(n log n)解,因为可能存在O(n²)三元组此属性(想一想所有的字符串,大约有n²/ 2这样的三元组)。当然,您只在寻找其中之一,但是我目前不知道如何找到它...


是的,我们正在讨论问题的较难版本。但是,n * log(n)解决方案仍然可能。
Olexiy

1
实际上有n个选择3,这是O(n ^ 3)个可能的三元组,我
想当

@gmatt:n选择2就足够了;如果我们固定两个1,则确定第三个的位置,并且恒定时间查看该位置是否有1。
ShreevatsaR

@ShreevatsaR:是的,我认为这是正确的,我正在考虑不受限制的情况。
ldog

1
@gmatt:实际上,我们正在寻找具有0 <= i <(n-3)和0 <g <(ni-1)/ 2的约束的上述定义的元组(i,g),因此对n ^ 2/2 ...
MartinStettner,2009年

1

一个有趣的问题,但是一旦您意识到两个“ 1”之间的实际模式无关紧要,该算法将变为:

  • 扫描寻找“ 1”
  • 从下一个位置扫描开始,再扫描另一个“ 1”(到数组末尾减去与当前第一个“ 1”的距离,否则第三个“ 1”将超出范围)
  • 如果在第二个“ 1”的位置加上到第一个“ 1”的距离处找到了第三个“ 1”,则我们有一个均匀的间隔。

在代码中,采用JTest方式(请注意,这段代码并不是最有效的,我添加了一些println以查看会发生什么。)

import java.util.Random;

import junit.framework.TestCase;

public class AlgorithmTest extends TestCase {

 /**
  * Constructor for GetNumberTest.
  *
  * @param name The test's name.
  */
 public AlgorithmTest(String name) {
  super(name);
 }

 /**
  * @see TestCase#setUp()
  */
 protected void setUp() throws Exception {
  super.setUp();
 }

 /**
  * @see TestCase#tearDown()
  */
 protected void tearDown() throws Exception {
  super.tearDown();
 }

 /**
  * Tests the algorithm.
  */
 public void testEvenlySpacedOnes() {

  assertFalse(isEvenlySpaced(1));
  assertFalse(isEvenlySpaced(0x058003));
  assertTrue(isEvenlySpaced(0x07001));
  assertTrue(isEvenlySpaced(0x01007));
  assertTrue(isEvenlySpaced(0x101010));

  // some fun tests
  Random random = new Random();

  isEvenlySpaced(random.nextLong());
  isEvenlySpaced(random.nextLong());
  isEvenlySpaced(random.nextLong());
 }

 /**
  * @param testBits
  */
 private boolean isEvenlySpaced(long testBits) {
  String testString = Long.toBinaryString(testBits);
  char[] ones = testString.toCharArray();
  final char ONE = '1';

  for (int n = 0; n < ones.length - 1; n++) {

   if (ONE == ones[n]) {
    for (int m = n + 1; m < ones.length - m + n; m++) {

     if (ONE == ones[m] && ONE == ones[m + m - n]) {
      System.out.println(" IS evenly spaced: " + testBits + '=' + testString);
      System.out.println("               at: " + n + ", " + m + ", " + (m + m - n));
      return true;
     }
    }
   }
  }

  System.out.println("NOT evenly spaced: " + testBits + '=' + testString);
  return false;
 }
}

4
如果我没记错的话,它是O(n²),因为外循环平均运行n次,内循环平均运行n / 2次。
StriplingWarrior

外循环平均运行n次,内循环平均运行n / 4,但仅从“ 1”之后的位置开始。要处理n ^ 2行为,'1'的数量必须很高,这会及早产生真实结果,从而停止处理。因此,n ^ 2行为将永远不会发生。目前,如何根据数据的已知属性确定O还是一个问题。
rsp

不幸的是,这与现实生活中的平均运行时间无关,而是理论上的Big O运行时。您的方法是O(n²)(与我的方法相同,因为您的方法与我的方法相同)
DaClown

我不是在谈论平均行为,而是最大行为。如果可以证明在测试中失败的最大熵在字符串中包含log n'1,我不会感到惊讶。
rsp

如果用内部循环中找到的第一个索引更新外部循环中的索引,即if(ones [m] == ONE){n = m},该怎么办?这对大O有用吗?
steamer25年9

1

我想到了一种可行的分而治之的方法。

首先,在预处理中,您需要将小于输入大小(n / 3)一半的所有数字插入列表中。

给定一个字符串:(0000010101000100请注意,此特定示例有效)

将从1到(16/2)的所有素数(和1)插入列表中:{1,2,3,4,5,6,7}

然后将其分成两半:

100000101 01000100

继续执行此操作,直到获得大小为1的字符串为止。对于其中所有大小为1的所有字符串,将其索引添加到可能性列表中;否则,将其添加为1。否则,返回-1返回失败。

您还需要返回与每个起始索引关联的仍然可能的间距列表。(从上面创建的列表开始,然后删除数字)。这里,空列表意味着您只处理一个1,因此此时可以有任何间距;否则,列表包括必须排除的间距。

因此,继续上面的示例:

1000 0101 0100 0100

10 00 01 01 01 00 01 00

1 0 0 0 0 1 0 1 0 1 0 0 0 1 0 0

在第一个合并步骤中,我们现在有八套两套。首先,我们有可能设置一个集合,但是我们知道由于存在另一个零,所以不可能以1间隔。因此我们返回0(用于索引)和{2,3,4,5,7},因为不可能用1隔开。在第二个中,我们什么都没有,因此返回-1。在第三个中,我们有一个匹配,索引5中没有消除任何间距,因此返回5,{1,2,3,4,5,7}。在第四对中,我们返回7,{1,2,3,4,5,7}。在第五行中,返回9,{1,2,3,4,5,7}。在第六行,返回-1。在第七行中,返回13,{1,2,3,4,5,7}。第八,返回-1。

再次组合成四组,每组四个,我们有:

1000:返回(0,{4,5,6,7}) 0101:返回(5,{2,3,4,5,6,7}),(7,{1,2,3,4,5,6 ,7}) 0100:返回(9,{3,4,5,6,7}) 0100:返回(13,{3,4,5,6,7})

分为八组:

10000101:返回(0,{5,7}),(5,{2,3,4,5,6,7}),(7,{1,2,3,4,5,6,7}) 01000100:返回(9,{4,7}),(13,{3,4,5,6,7})

合并为一组十六个:

10000101 01000100

随着我们的进步,我们一直在检查到目前为止的所有可能性。到这一步为止,我们剩下的内容超出了字符串的末尾,但是现在我们可以检查所有可能性了。

基本上,我们以5和7的间隔检查第一个1,并发现它们未与1对齐。(请注意,每个检查都是恒定的,不是线性时间),然后我们检查第二个(索引5),其间隔为2、3、4、5、6和7,或者我们可以,但是我们可以在2处停止,因为实际上匹配。

!这是一个相当长的算法。

由于最后一步,我不知道它是否为O(n log n),但据我所知,到那里的一切肯定都是O(n log n)。稍后,我将再次讨论并尝试完善最后一步。

编辑:更改了我的答案以反映Welbog的评论。对不起,错误。当我有更多的时间来解密我再次写的内容时,我也会稍后编写一些伪代码。;-)


我没有遵循您的算法,但是为尝试实际尝试成为O(n log n)的算法+1
ldog

谢谢。当我有更多时间(也许写一些伪代码或其他东西)时,我会尝试更好地解释它。
白金Azure

为什么只看素数的缺口可能性?您将如何建议匹配类似的字符串100010001?如果我正确理解您的方法,则将无法匹配它,因为(0,{4})无法计算出正确的答案。我认为,考虑到列表中需要非素数,很容易提出病理性字符串,使需要检查的可能性列表膨胀到O(n log(n))以上。
Welbog

发誓好吧,我本来打算做倍数的,但是我在中途改变了答案,却没有去改变一切。抱歉。将解决短期
白金天青

3
我不认为这是O(n log n)。在第一个合并步骤中,您将处理(n / 2)个集合,每个集合都可能返回O(n)个可能的间距集合。不幸的是,仅此一项就使其变为O(n ^ 2)。
MartinStettner,2009年

1

我将在此处给出大概的猜测,并让那些在计算复杂度上更出色的人帮助我理解算法在O表示法方面的表现

  1. 给定二进制字符串0000010101000100(例如)
  2. 作物头和尾的零-> 00000 101010001 00
  3. 我们从以前的计算中得到101010001
  4. 检查中间位是否为“ 1”,如果为true,则发现有效的三个均匀间隔的“ 1”(仅当位数为奇数时)
  5. 相关地,如果剩余的裁剪位数是偶数,则头和尾的“ 1”不能成为等距的“ 1”的一部分,
  6. 我们以1010100001为例(带有一个额外的“零”成为偶数裁剪),在这种情况下,我们需要再次裁剪,然后变为-> 10101 00001
  7. 我们从先前的计算中得到10101,并检查中间位,然后再次找到等距位

我不知道如何计算复杂度,有人可以帮忙吗?

编辑:添加一些代码来说明我的想法

edit2:尝试编译我的代码,发现一些主要错误,已修复

char *binaryStr = "0000010101000100";

int main() {
   int head, tail, pos;
   head = 0;
   tail = strlen(binaryStr)-1;
   if( (pos = find3even(head, tail)) >=0 )
      printf("found it at position %d\n", pos);
   return 0;
}

int find3even(int head, int tail) {
   int pos = 0;
   if(head >= tail) return -1;
   while(binaryStr[head] == '0') 
      if(head<tail) head++;
   while(binaryStr[tail] == '0') 
      if(head<tail) tail--;
   if(head >= tail) return -1;
   if( (tail-head)%2 == 0 && //true if odd numbered
       (binaryStr[head + (tail-head)/2] == '1') ) { 
         return head;
   }else {
      if( (pos = find3even(head, tail-1)) >=0 )
         return pos;
      if( (pos = find3even(head+1, tail)) >=0 )
         return pos;
   }
   return -1;
}

@recursive我认为当它到达调用find3even(head + 1,tail)时它将起作用,然后将其裁剪为head = 4时变为111。您能再次检查一下吗?
andycjw

@recursive请检查我添加的代码,以更好地解释我之前编写的伪代码,它不是很严格和简洁
andycjw

这是nlogn-对于n位,我们期望大约logn次迭代检查n * c位,其中C为常数。
罗恩·沃霍里克 Ron Warholic),2009年

是的,最简单的情况似乎在111001和100111上失败。等距的1不必居中居中。
院长J,

它可以正确处理这些情况,111001的位数为偶数,因此会立即分为111和001。由于111的位数为奇数,中间的位数为1,因此它会成功返回。
罗恩·沃霍里克

1

我想到了这样的东西:

def IsSymetric(number):
    number = number.strip('0')

    if len(number) < 3:
        return False
    if len(number) % 2 == 0:
        return IsSymetric(number[1:]) or IsSymetric(number[0:len(number)-2])
    else:
        if number[len(number)//2] == '1':
            return True
        return IsSymetric(number[:(len(number)//2)]) or IsSymetric(number[len(number)//2+1:])
    return False

这是受andycjw启发的。

  1. 截断零。
  2. 如果是偶数,则测试两个子字符串0-(len-2)(跳过最后一个字符)和从1-(len-1)(跳过第一个字符)
  3. 如果不是,即使中间字符是一个字符,我们也会成功。否则,在不包含midle元素的情况下将字符串分割成midle,然后检查两个部分。

至于复杂度,可能是O(nlogn),因为在每次递归中,我们都将其除以2。

希望能帮助到你。


看来您要将N个元素的问题转换为N-1个元素的2个问题。将其分成两半意味着将其转换为2个具有N / 2个元素的问题。
RHSeeger

只有偶数长度才如此。因此,如果len为8,则算法将创建长度为7、7、3、3、3、3的字符串。递归树的hight为3,等于lg(8)。
Beku

1

好的,我将再次解决这个问题。我想我可以证明O(n log(n))算法类似于已经讨论的O(n log(n))算法,方法是使用平衡二叉树存储1之间的距离。这种方法的灵感来自于大法官关于将问题缩小到1之间的距离列表的观察。

我们是否可以扫描输入字符串以在1的位置周围构造一个平衡的二叉树,以便每个节点存储1的位置,并且每个边用每个子节点到相邻1的距离标记。例如:

10010001 gives the following tree

      3
     / \
  2 /   \ 3
   /     \
  0       7

这可以在O(n log(n))中完成,因为在最坏的情况下,对于大小为n的字符串,每次插入都需要O(log(n))。

然后问题是搜索树以发现在任何节点处是否存在从该节点到左子节点的路径,该路径与通过右子节点的路径具有相同的距离。可以在每个子树上递归地完成此操作。在搜索中合并两个子树时,我们必须将距左子树中路径的距离与距右树中路径的距离进行比较。由于子树中的路径数与log(n)成正比,并且节点数为n,因此我相信可以在O(n log(n))时间内完成。

我有想念吗?


“因为子树中的路径数与log(n)成正比”,为什么不n?通常,这是一种很有前途的方法。
sdcvvc

@sdcwc:它与log(n)成正比,而不与n成正比,因为在平衡树中,每个子树都有一半的节点,并且到该子树的根的路径数与该子树中的节点数相同(不包括根)。
Jeremy Bourque

0

这似乎是一个有趣的问题,所以我决定尝试一下。

我假设111000001将找到前三个并成功。从本质上讲,紧跟1的零的数目很重要,因为根据您的定义0111000与111000相同。一旦找到两个1的情况,下一个1将完成三部曲。

在Python中:

def find_three(bstring):
    print bstring
    dict = {}
    lastone = -1
    zerocount = 0
    for i in range(len(bstring)):
        if bstring[i] == '1':
            print i, ': 1'
            if lastone != -1:
                if(zerocount in dict):
                    dict[zerocount].append(lastone)
                    if len(dict[zerocount]) == 2:
                        dict[zerocount].append(i)
                        return True, dict
                else:
                    dict[zerocount] = [lastone]
            lastone = i
            zerocount = 0
        else:
            zerocount = zerocount + 1
    #this is really just book keeping, as we have failed at this point
    if lastone != -1:
        if(zerocount in dict):
            dict[zerocount].append(lastone)
        else:
            dict[zerocount] = [lastone]
    return False, dict

这是第一次尝试,因此我相信可以用一种更简洁的方式编写它。请在下面列出此方法失败的情况。


@recursive,这些间距不均匀。
詹姆斯·麦克马洪

均匀间隔是什么意思?看一下索引0、3和6。全为1,两个分别分开。
递归

噢,据我所知,零仅包含在间隔中。
James McMahon

该问题确实提到了“ 1001011”,这对它不起作用。提出问题后立即发布了一个较早的(现在已删除)答案,该答案解决了与此问题相同的(其他)问题。:-)
ShreevatsaR

我今天在工作时正在查看此内容,但我不明白Rob对他的编辑的含义。为了清楚起见,我已经编辑了问题。我应该知道,当我度过轻松的时光时,我会丢失一些东西。
詹姆斯·麦克马洪

0

我认为这是nlog(n)的原因是由于以下原因:

  • 要查找作为三元组开始的1,您需要检查(n-2)个字符。如果到那时仍未找到它,则不会(字符n-1和n无法启动三元组)(O(n))
  • 要查找作为三元组的一部分的第二个1(从第一个1开始),您需要检查m / 2(m = nx,其中x是第一个1的偏移量)字符。这是因为,如果您在从第一个1到终点的中途还没有找到第二个1,您就不会...因为第三个1必须与第二个1完全相同。(O(log(n)))
  • 因为您知道在找到第一个和第二个索引之前它必须位于的索引,所以O(1)可以找到最后一个1。

因此,您有n,log(n)和1 ... O(nlogn)

编辑:糟糕,我不好。我的大脑将n / 2设置为logn ...这显然不是(将项目数加倍仍将内循环的迭代次数加倍)。这仍然是n ^ 2,无法解决问题。好吧,至少我要写一些代码:)


在Tcl中实施

proc get-triplet {input} {
    for {set first 0} {$first < [string length $input]-2} {incr first} {
        if {[string index $input $first] != 1} {
            continue
        }
        set start [expr {$first + 1}]
        set end [expr {1+ $first + (([string length $input] - $first) /2)}]
        for {set second $start} {$second < $end} {incr second} {
            if {[string index $input $second] != 1} {
                continue
            }
            set last [expr {($second - $first) + $second}]
            if {[string index $input $last] == 1} {
                return [list $first $second $last]
            }
        }
    }
    return {}
}

get-triplet 10101      ;# 0 2 4
get-triplet 10111      ;# 0 2 4
get-triplet 11100000   ;# 0 1 2
get-triplet 0100100100 ;# 1 4 7

0

我想我已经找到解决问题的方法,但是我无法构造正式的证明。我提出的解决方案是用Java编写的,它使用计数器'n'来计算其执行的列表/数组访问次数。因此,如果n正确,则应小于或等于stringLength * log(stringLength)。我尝试将其设置为0到2 ^ 22的数字,并且有效。

它从遍历输入字符串开始,并列出所有包含一个索引的索引。这只是O(n)。

然后从索引列表中选择一个firstIndex和一个大于firstIndex的secondIndex。这两个索引必须包含一个,因为它们在索引列表中。从那里可以计算thirdIndex。如果inputString [thirdIndex]为1,则暂停。

public static int testString(String input){
//n is the number of array/list accesses in the algorithm
int n=0;

//Put the indices of all the ones into a list, O(n)
ArrayList<Integer> ones = new ArrayList<Integer>();
for(int i=0;i<input.length();i++){
    if(input.charAt(i)=='1'){
        ones.add(i);
    }
}

//If less than three ones in list, just stop
if(ones.size()<3){
    return n;
}

int firstIndex, secondIndex, thirdIndex;
for(int x=0;x<ones.size()-2;x++){
    n++;
    firstIndex = ones.get(x);

    for(int y=x+1; y<ones.size()-1; y++){
        n++;
        secondIndex = ones.get(y);
        thirdIndex = secondIndex*2 - firstIndex;

        if(thirdIndex >= input.length()){
            break;
        }

        n++;
        if(input.charAt(thirdIndex) == '1'){
            //This case is satisfied if it has found three evenly spaced ones
            //System.out.println("This one => " + input);
            return n;
        }
    }
}

return n;

}

附加说明:在对输入字符串进行迭代以构造索引列表时,计数器n不会增加。此运算为O(n),因此无论如何都不会影响算法的复杂性。


您似乎仍然有两个嵌套的O(n)循环,这使其成为O(n ^ 2)
RHSeeger 2009年

索引数组的大小与输入字符串的大小不同。这使我很难写出真实的证明或证明它是不正确的。我怀疑有一些基本的数学思想可以使这项工作奏效。
罗伯特·帕克,2009年

1
我认为解决此问题的技巧是,尽管您的算法为O(n ^ 2),但您可能获得的字符串的最坏情况只会导致O(nlogn)迭代,否则您将找到使用该算法的解决方案。
z-

2
测试高达2 ^ 22并不能真正测试其复杂性。2 ^ 22仅具有22位,这意味着您的N为22。尝试输入一些值,其中N为几百万。
Peter Recore 09年

1
尝试使用yx答案中给出的最大“不良”字符串之一的此算法,您会发现这是一种O(n^2)算法。
Welbog

0

进入该问题的一种途径是考虑因素并进行转变。

通过移位,可以将一和零的字符串与自身的移位版本进行比较。然后,您选择匹配的。举个例子,这个例子移位了两个:

1010101010
  1010101010
------------
001010101000

结果1(按位与)必须代表所有等间隔为2的1。同一示例移动了三个:

1010101010
   1010101010
-------------
0000000000000

在这种情况下,没有1被均匀地隔开三个。

那这告诉你什么呢?好吧,您只需要测试作为质数的班次即可。例如,假设您有两个1,两个相距六个。您只需要测试“两个”班次和“三个”班次(因为这些班次除以六)。例如:

10000010 
  10000010 (Shift by two)
    10000010
      10000010 (We have a match)

10000010
   10000010 (Shift by three)
      10000010 (We have a match)

因此,您唯一需要检查的移位是2,3,5,7,11,13等。直到最接近数字串大小平方根的质数。

快解决了吗?

我想我更接近解决方案。基本上:

  1. 扫描字符串中的1。对于每个1音符,取其位置模数后的余数。模量范围为琴弦大小的1至一半。这是因为最大可能的分隔大小是字符串的一半。这是在O(n ^ 2)中完成的。但。只需要检查素数模量,因此O(n ^ 2 / log(n))
  2. 首先按最大模数的顺序对模数/余数列表进行排序,这可以在O(n * log(n))时间内完成。
  3. 寻找三个相同的连续模数/余数。
  4. 不知何故找回了他们的位置!

我认为答案的最大线索是,最快的排序算法是O(n * log(n))。

错误

同事指出,步骤1是错误的。如果在位置2,12和102处有1,则取10的模数,它们的余数都相同,但间隔不相等!抱歉。


这是一种有趣的方法,如果您想出一个完整的解决方案,请告诉我们。
詹姆斯·麦克马洪

移位一个数k O(n)次,然后每次移位O(n)检查都会产生O(n ^ 2)算法,即使您移位一个数也是如此。您的算法将必须移动不止一个数字。
ldog

0

这里有一些想法,尽管我已尽力而为,但似乎仍未如愿以偿。不过,它们可能是某人进行分析的有用起点。

请考虑以下提议的解决方案,这是一些人(包括我自己)在此答案的先前版本中提出的方法。 :)

  1. 修剪前导零和尾随零。
  2. 扫描字符串以查找1。
  3. 找到1时:
    1. 假定它是解决方案的中间1。
    2. 对于每个先验1,使用其保存的位置来计算最终1的预期位置。
    3. 如果计算出的位置在字符串的末尾之后,则它不能成为解决方案的一部分,因此请将位置从候选列表中删除。
    4. 检查解决方案。
  4. 如果找不到解决方案,请将当前的1添加到候选列表中。
  5. 重复直到找不到更多1。

现在考虑如下的输入字符串字符串,它将没有解决方案:

101
101001
1010010001
101001000100001
101001000100001000001

通常,这是k个字符串的串联,形式为j 0',后跟j(从0到k-1)的1。

k=2  101
k=3  101001
k=4  1010010001
k=5  101001000100001
k=6  101001000100001000001

请注意,子字符串的长度为1、2、3等。因此,问题大小n的子字符串长度为1至k,因此n = k(k + 1)/ 2。

k=2  n= 3  101
k=3  n= 6  101001
k=4  n=10  1010010001
k=5  n=15  101001000100001
k=6  n=21  101001000100001000001

请注意,k还跟踪我们必须考虑的1的数量。请记住,每次看到1时,我们都需要考虑到目前为止看到的所有1。因此,当我们看到第二个1时,我们只考虑第一个1,当我们看到第三个1时,我们重新考虑前两个,当我们看到第四个1时,我们需要重新考虑前三个,依此类推。到算法结束时,我们已经考虑了k(k-1)/ 2对1。叫那个p。

k=2  n= 3  p= 1  101
k=3  n= 6  p= 3  101001
k=4  n=10  p= 6  1010010001
k=5  n=15  p=10  101001000100001
k=6  n=21  p=15  101001000100001000001

n和p之间的关系是n = p + k。

遍历字符串的过程需要O(n)时间。每次遇到1时,最多进行(k-1)个比较。由于n = k(k + 1)/ 2,n> k ** 2,所以sqrt(n)> k。这给我们O(n sqrt(n))或O(n ** 3/2)。但是请注意,这可能不是一个很严格的界限,因为比较次数从1到最大为k,并不是一直都是k。但是我不确定如何在数学中说明这一点。

它仍然不是O(n log(n))。另外,尽管我怀疑它们是最坏的情况,但我无法证明这些输入是最坏的情况。我认为最前面的1的密集包装会导致最后的包装更加均匀。

由于有人可能仍然觉得它有用,因此这是我在Perl中解决方案的代码:

#!/usr/bin/perl

# read input as first argument
my $s = $ARGV[0];

# validate the input
$s =~ /^[01]+$/ or die "invalid input string\n";

# strip leading and trailing 0's
$s =~ s/^0+//;
$s =~ s/0+$//;

# prime the position list with the first '1' at position 0
my @p = (0);

# start at position 1, which is the second character
my $i = 1;

print "the string is $s\n\n";

while ($i < length($s)) {
   if (substr($s, $i, 1) eq '1') {
      print "found '1' at position $i\n";
      my @t = ();
      # assuming this is the middle '1', go through the positions
      # of all the prior '1's and check whether there's another '1'
      # in the correct position after this '1' to make a solution
      while (scalar @p) {
         # $p is the position of the prior '1'
         my $p = shift @p;
         # $j is the corresponding position for the following '1'
         my $j = 2 * $i - $p;
         # if $j is off the end of the string then we don't need to
         # check $p anymore
         next if ($j >= length($s));
         print "checking positions $p, $i, $j\n";
         if (substr($s, $j, 1) eq '1') {
            print "\nsolution found at positions $p, $i, $j\n";
            exit 0;
         }
         # if $j isn't off the end of the string, keep $p for next time
         push @t, $p;
      }
      @p = @t;
      # add this '1' to the list of '1' positions
      push @p, $i;
   }
   $i++;
}

print "\nno solution found\n";

您的“非解决方案”顺序是错误的;每个1的索引是三角形数字1、3、6、10、15等的序列,并且包括形成算术级数的数字6、36和66。
詹森·S

0

扫描1时,将其位置添加到列表中。当添加第二个和连续的1时,请将它们与列表中的每个位置进行比较。间距等于currentOne(中心)-previousOne(左)。右侧位是currentOne +间距。如果为1,则结束。

列表与它们之间的空间成反比。简而言之,如果1之间有很多0(在最坏的情况下),则已知1的列表将增长得非常慢。

using System;
using System.Collections.Generic;

namespace spacedOnes
{
    class Program
    {
        static int[] _bits = new int[8] {128, 64, 32, 16, 8, 4, 2, 1};

        static void Main(string[] args)
        {
            var bytes = new byte[4];
            var r = new Random();
            r.NextBytes(bytes);
            foreach (var b in bytes) {
                Console.Write(getByteString(b));
            }
            Console.WriteLine();
            var bitCount = bytes.Length * 8;
            var done = false;
            var onePositions = new List<int>();
            for (var i = 0; i < bitCount; i++)
            {
                if (isOne(bytes, i)) {
                    if (onePositions.Count > 0) {
                        foreach (var knownOne in onePositions) {
                            var spacing = i - knownOne;
                            var k = i + spacing;
                            if (k < bitCount && isOne(bytes, k)) {
                                Console.WriteLine("^".PadLeft(knownOne + 1) + "^".PadLeft(spacing) + "^".PadLeft(spacing));
                                done = true;
                                break;
                            }
                        }
                    }
                    if (done) {
                        break;
                    }
                    onePositions.Add(i);
                }
            }
            Console.ReadKey();
        }

        static String getByteString(byte b) {
            var s = new char[8];
            for (var i=0; i<s.Length; i++) {
                s[i] = ((b & _bits[i]) > 0 ? '1' : '0');
            }
            return new String(s);
        }

        static bool isOne(byte[] bytes, int i)
        {
            var byteIndex = i / 8;
            var bitIndex = i % 8;
            return (bytes[byteIndex] & _bits[bitIndex]) > 0;
        }
    }
}

0

我以为我会在发布第22个天真的解决方案之前添加一条评论。对于幼稚的解决方案,我们不需要显示字符串中的1的数量最多为O(log(n)),而是最多为O(sqrt(n * log(n))。

求解器:

def solve(Str):
    indexes=[]
    #O(n) setup
    for i in range(len(Str)):
        if Str[i]=='1':
            indexes.append(i)

    #O((number of 1's)^2) processing
    for i in range(len(indexes)):
        for j in range(i+1, len(indexes)):
                            indexDiff = indexes[j] - indexes[i]
            k=indexes[j] + indexDiff
            if k<len(Str) and Str[k]=='1':
                return True
    return False

尽管向前看而不是向后看,但这基本上与flybywire的想法和实现有点相似。

贪婪的字符串生成器:

#assumes final char hasn't been added, and would be a 1 
def lastCharMakesSolvable(Str):
    endIndex=len(Str)
    j=endIndex-1
    while j-(endIndex-j) >= 0:
        k=j-(endIndex-j)
        if k >= 0 and Str[k]=='1' and Str[j]=='1':
            return True
        j=j-1
    return False



def expandString(StartString=''):
    if lastCharMakesSolvable(StartString):
        return StartString + '0'
    return StartString + '1'

n=1
BaseStr=""
lastCount=0
while n<1000000:
    BaseStr=expandString(BaseStr)
    count=BaseStr.count('1')
    if count != lastCount:
        print(len(BaseStr), count)
    lastCount=count
    n=n+1

(以我的辩护,我仍处于“学习Python”的理解阶段)

同样,从贪婪的字符串构建中获得潜在有用的输出,在1的数量达到2的幂之后会有相当一致的跳跃...我不想等到2096年。

strlength   # of 1's
    1    1
    2    2
    4    3
    5    4
   10    5
   14    8
   28    9
   41    16
   82    17
  122    32
  244    33
  365    64
  730    65
 1094    128
 2188    129
 3281    256
 6562    257
 9842    512
19684    513
29525    1024

0

我将尝试提出一种数学方法。这更多的是起点,而不是终点,因此,任何帮助,评论甚至矛盾都将得到深深的赞赏。但是,如果证明了这种方法-该算法是对字符串的直接搜索。

  1. 给定固定数量的空格k和字符串S,搜索k个间隔的三元组需要O(n)-我们只需测试每个0<=i<=(n-2k)if S[i]==S[i+k]==S[i+2k]。测试需要花费时间,O(1)而我们在n-k时间k为常数的地方进行测量,因此需要花费时间O(n-k)=O(n)

  2. 让我们假设的数量之间存在反比例 1和我们需要搜索的最大空间。也就是说,如果有许多个1,则必须有一个三元组并且必须相当密集。如果只有几个1,则三元组(如果有的话)可能很稀疏。换句话说,我可以证明如果我有足够1的三元组,那么必须存在这样的三元组,而1我拥有的三元组中的数量更多,则必须找到更密集的三元组。这可以用鸽洞原理来解释-希望以后再详细说明。

  3. 假设k我要查找的可能空间数量有一个上限。现在,对于每个1位于的位置,S[i]我们需要检查1in S[i-1]S[i+1]S[i-2]and S[i+2],... S[i-k]S[i+k]。这需要O((k^2-k)/2)=O(k^2)每个1S-由于高斯系列求和公式。请注意,这与第1节有所不同-我k将空格数量作为上限,而不是恒定空格。

我们需要证明O(n*log(n))。也就是说,我们需要证明与k*(number of 1's)成正比log(n)

如果我们能做到这一点,该算法是微不足道的-每一个1S其索引中i,只需找1从每一侧查找到距离即可k。如果在相同距离内找到两个,则返回ik。同样,棘手的部分是找到k并证明正确性。

非常感谢您在这里提出的意见-我一直在努力寻找白板上k的数量与之间的关系1,但到目前为止没有成功。


0

假设:

只是错误,谈论log(n)的上限数

编辑:

现在,我发现使用Cantor数(如果正确),集合上的密度为(2/3)^ Log_3(n)(这是一个怪异的函数),我同意,log(n)/ n密度很强。

如果这是上限,那么至少有O(n *(3/2)^(log(n)/ log(3)))时间复杂度和O((3/2)^( log(n)/ log(3)))空间复杂度。(请查看大法官对algorhitm的回答)

到目前为止,它仍然比O(n ^ 2)好

乍一看,这个函数((3/2)^(log(n)/ log(3)))实际上看起来像n * log(n)。

我怎么得到这个公式?

在字符串上应用Cantors编号。
假设字符串的长度为3 ^ p == n
在生成Cantor字符串的每一步中,您将保留上一个数目的2/3。应用此p次。

意思是(n *((2/3)^ p))->((((3 ^ p))*((2/3)^ p))剩余的余数,然后简化为2 ^ p。这意味着3 ^ p字符串中的2 ^ p个->(3/2)^ p个。替换p = log(n)/ log(3)并得到
((3/2)^(log(n)/ log(3)))


False:Cantor集的密度为n ^ log_3(2)。
sdcvvc

0

具有O(n ^ 2)空间的简单O(n)解决方案怎么样?(使用所有按位运算符都在O(1)中工作的假设。)

该算法基本上分为四个阶段:

阶段1:对于原始号码的每一位,找出它们有多远,但仅考虑一个方向。(我考虑了所有最低有效位的方向。)

阶段2:颠倒输入中的位顺序;

阶段3:在反向输入上重新运​​行步骤1。

第4阶段:比较第1阶段和第3阶段的结果。如果任何比特在“上”和“下”之间均等间隔,则必须命中。

请记住,以上算法中的任何步骤都不会花费超过O(n)的时间。^ _ ^

另外一个好处是,该算法将从每个数字中查找所有等距的数字。因此,例如,如果您得到的结果为“ 0x0005”,则等距的间隔均为1和3个单位

我没有真正尝试优化下面的代码,但它似乎是可编译的C#代码。

using System;

namespace ThreeNumbers
{
    class Program
    {
        const int uint32Length = 32;

        static void Main(string[] args)
        {
            Console.Write("Please enter your integer: ");
            uint input = UInt32.Parse(Console.ReadLine());

            uint[] distancesLower = Distances(input);
            uint[] distancesHigher = Distances(Reverse(input));

            PrintHits(input, distancesLower, distancesHigher);
        }

        /// <summary>
        /// Returns an array showing how far the ones away from each bit in the input.  Only 
        /// considers ones at lower signifcant bits.  Index 0 represents the least significant bit 
        /// in the input.  Index 1 represents the second least significant bit in the input and so 
        /// on.  If a one is 3 away from the bit in question, then the third least significant bit 
        /// of the value will be sit.
        /// 
        /// As programed this algorithm needs: O(n) time, and O(n*log(n)) space.  
        /// (Where n is the number of bits in the input.)
        /// </summary>
        public static uint[] Distances(uint input)
        {
            uint[] distanceToOnes = new uint[uint32Length];
            uint result = 0;

            //Sets how far each bit is from other ones. Going in the direction of LSB to MSB
            for (uint bitIndex = 1, arrayIndex = 0; bitIndex != 0; bitIndex <<= 1, ++arrayIndex)
            {
                distanceToOnes[arrayIndex] = result;
                result <<= 1;

                if ((input & bitIndex) != 0)
                {
                    result |= 1;
                }
            }

            return distanceToOnes;
        }

        /// <summary>
        /// Reverses the bits in the input.
        /// 
        /// As programmed this algorithm needs O(n) time and O(n) space.  
        /// (Where n is the number of bits in the input.)
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static uint Reverse(uint input)
        {
            uint reversedInput = 0;
            for (uint bitIndex = 1; bitIndex != 0; bitIndex <<= 1)
            {
                reversedInput <<= 1;
                reversedInput |= (uint)((input & bitIndex) != 0 ? 1 : 0);
            }

            return reversedInput;
        }

        /// <summary>
        /// Goes through each bit in the input, to check if there are any bits equally far away in 
        /// the distancesLower and distancesHigher
        /// </summary>
        public static void PrintHits(uint input, uint[] distancesLower, uint[] distancesHigher)
        {
            const int offset = uint32Length - 1;

            for (uint bitIndex = 1, arrayIndex = 0; bitIndex != 0; bitIndex <<= 1, ++arrayIndex)
            {
                //hits checks if any bits are equally spaced away from our current value
                bool isBitSet = (input & bitIndex) != 0;
                uint hits = distancesLower[arrayIndex] & distancesHigher[offset - arrayIndex];

                if (isBitSet && (hits != 0))
                {
                    Console.WriteLine(String.Format("The {0}-th LSB has hits 0x{1:x4} away", arrayIndex + 1, hits));
                }
            }
        }
    }
}

有人可能会评论说,对于任何足够大的数字,在O(1)中都无法完成按位运算。你说的没错。但是,我猜想每个使用加法,减法,乘法或除法(不能通过移位完成)的解决方案也都将存在该问题。


0

下面是一个解决方案。到处可能会有一些小错误,但是这个想法是正确的。

编辑:它不是n * log(n)

伪代码:

foreach character in the string
  if the character equals 1 {         
     if length cache > 0 { //we can skip the first one
        foreach location in the cache { //last in first out kind of order
           if ((currentlocation + (currentlocation - location)) < length string)
              if (string[(currentlocation + (currentlocation - location))] equals 1)
                 return found evenly spaced string
           else
              break;
        }
     }
     remember the location of this character in a some sort of cache.
  }

return didn't find evenly spaced string

C#代码:

public static Boolean FindThreeEvenlySpacedOnes(String str) {
    List<int> cache = new List<int>();

    for (var x = 0; x < str.Length; x++) {
        if (str[x] == '1') {
            if (cache.Count > 0) {
                for (var i = cache.Count - 1; i > 0; i--) {
                    if ((x + (x - cache[i])) >= str.Length)
                        break;

                    if (str[(x + (x - cache[i]))] == '1')
                        return true;                            
                }
            }
            cache.Add(x);                    
        }
    }

    return false;
}

这个怎么运作:

iteration 1:
x
|
101101001
// the location of this 1 is stored in the cache

iteration 2:
 x
 | 
101101001

iteration 3:
a x b 
| | | 
101101001
//we retrieve location a out of the cache and then based on a 
//we calculate b and check if te string contains a 1 on location b

//and of course we store x in the cache because it's a 1

iteration 4:
  axb  
  |||  
101101001

a  x  b  
|  |  |  
101101001


iteration 5:
    x  
    |  
101101001

iteration 6:
   a x b 
   | | | 
101101001

  a  x  b 
  |  |  | 
101101001
//return found evenly spaced string

1
您的算法有效,但是您需要证明它小于O(n ^ 2)。琐碎的分析使您达到O(n ^ 2)。对于每个1,您都要遍历之前的所有1。使复杂度函数为1 + 2 + 3 + ... +(k / 2-1)= O(k ^ 2)[其中k为1的数量]。
安娜

我检查了一下,确实没有解决方案的最坏情况场景大于O(n * log(n))
Niek H.

0

显然,我们至少需要同时检查一堆三胞胎,因此我们需要以某种方式压缩检查。我有一个候选算法,但是分析时间复杂度超出了我的能力*时间阈值。

构建一棵树,其中每个节点有三个子节点,每个节点在叶子处包含1的总数。还要在1上建立一个链表。为每个节点分配与其覆盖范围成比例的允许成本。只要我们在每个节点上花费的时间都在预算之内,我们就可以使用O(n lg n)算法。

-

从根开始。如果小于1的总数的平方小于其允许的成本,则应用朴素算法。否则,递归其子级。

现在我们要么在预算之内退回,要么我们知道其中一个孩子没有完全包含有效的三胞胎。因此,我们必须检查节点间三元组。

现在事情变得非常混乱。我们本质上是想在限制范围的同时递归潜在的子集。一旦范围受到足够的限制,以至于天真的算法将在预算范围内运行,您就可以执行此操作。享受实现这一点,因为我保证这将是乏味的。大概有十几个案例。

-

我认为该算法会起作用的原因是,没有有效三元组的序列似乎在成串的1和很多0之间交替出现。它有效地分割了附近的搜索空间,并且树模拟了该分割。

该算法的运行时间一点也不明显。它依赖于序列的非平凡特性。如果1真的很稀疏,那么朴素算法将在预算范围内工作。如果1密集,则应立即找到匹配项。但是,如果密度是“恰到好处”(例如,在〜n ^ 0.63附近,您可以通过将所有位设置为基数3中没有“ 2”位的位置来实现),我不知道它是否会起作用。您必须证明分裂效果足够强。


0

这里没有理论上的答案,但是我写了一个快速的Java程序来探索运行时行为与k和n的关系,其中n是总位长,k是1的数量。我和一些回答者一起说,“常规”算法检查所有成对的比特位置并寻找第3个比特,即使在最坏的情况下它需要O(k ^ 2),在现实,因为最坏的情况需要稀疏的位串,所以是O(n ln n)。

无论如何,这是下面的程序。这是一个蒙特卡洛风格的程序,它对常数n进行大量试验NTRIALS,并使用伯努利过程(其密度可在指定的极限之间进行限制)使用伯努利过程随机生成一系列k值的位集,并记录运行时间查找或未能找到等间隔的三元组的时间,而不是CPU时间的步长。我针对n = 64、256、1024、4096、16384 *(仍在运行)运行了它,首先进行了500000次试验,以查看哪些k值花费了最长的运行时间,然后又进行了一次5000000次试验(采用了狭窄的试验),集中精力看一下这些值是什么样的。最长的运行时间确实发生在非常稀疏的密度上(例如,对于n = 4096,运行时间峰值在k = 16-64范围内,对于平均运行时间,在4212步长处出现一个温和的峰值@ k = 31,最大运行时间在5101步@ k = 58时达到峰值)。看起来,在最坏情况下的O(k ^ 2)步骤要比在扫描位串以查找1的位置索引的O(n)步骤要大的情况下,将需要非常大的N值。

package com.example.math;

import java.io.PrintStream;
import java.util.BitSet;
import java.util.Random;

public class EvenlySpacedOnesTest {
    static public class StatisticalSummary
    {
        private int n=0;
        private double min=Double.POSITIVE_INFINITY;
        private double max=Double.NEGATIVE_INFINITY;
        private double mean=0;
        private double S=0;

        public StatisticalSummary() {}
        public void add(double x) {
            min = Math.min(min, x);
            max = Math.max(max, x);
            ++n;
            double newMean = mean + (x-mean)/n;
            S += (x-newMean)*(x-mean);
            // this algorithm for mean,std dev based on Knuth TAOCP vol 2
            mean = newMean;
        }
        public double getMax() { return (n>0)?max:Double.NaN; }
        public double getMin() { return (n>0)?min:Double.NaN; }
        public int getCount() { return n; }
        public double getMean() { return (n>0)?mean:Double.NaN; }
        public double getStdDev() { return (n>0)?Math.sqrt(S/n):Double.NaN; } 
        // some may quibble and use n-1 for sample std dev vs population std dev    
        public static void printOut(PrintStream ps, StatisticalSummary[] statistics) {
            for (int i = 0; i < statistics.length; ++i)
            {
                StatisticalSummary summary = statistics[i];
                ps.printf("%d\t%d\t%.0f\t%.0f\t%.5f\t%.5f\n",
                        i,
                        summary.getCount(),
                        summary.getMin(),
                        summary.getMax(),
                        summary.getMean(),
                        summary.getStdDev());
            }
        }
    }

    public interface RandomBernoulliProcess // see http://en.wikipedia.org/wiki/Bernoulli_process
    {
        public void setProbability(double d);
        public boolean getNextBoolean();
    }

    static public class Bernoulli implements RandomBernoulliProcess
    {
        final private Random r = new Random();
        private double p = 0.5;
        public boolean getNextBoolean() { return r.nextDouble() < p; }
        public void setProbability(double d) { p = d; }
    }   
    static public class TestResult {
        final public int k;
        final public int nsteps;
        public TestResult(int k, int nsteps) { this.k=k; this.nsteps=nsteps; } 
    }

    ////////////
    final private int n;
    final private int ntrials;
    final private double pmin;
    final private double pmax;
    final private Random random = new Random();
    final private Bernoulli bernoulli = new Bernoulli();
    final private BitSet bits;
    public EvenlySpacedOnesTest(int n, int ntrials, double pmin, double pmax) {
        this.n=n; this.ntrials=ntrials; this.pmin=pmin; this.pmax=pmax;
        this.bits = new BitSet(n);
    }

    /*
     * generate random bit string
     */
    private int generateBits()
    {
        int k = 0; // # of 1's
        for (int i = 0; i < n; ++i)
        {
            boolean b = bernoulli.getNextBoolean();
            this.bits.set(i, b);
            if (b) ++k;
        }
        return k;
    }

    private int findEvenlySpacedOnes(int k, int[] pos) 
    {
        int[] bitPosition = new int[k];
        for (int i = 0, j = 0; i < n; ++i)
        {
            if (this.bits.get(i))
            {
                bitPosition[j++] = i;
            }
        }
        int nsteps = n; // first, it takes N operations to find the bit positions.
        boolean found = false;
        if (k >= 3) // don't bother doing anything if there are less than 3 ones. :(
        {       
            int lastBitSetPosition = bitPosition[k-1];
            for (int j1 = 0; !found && j1 < k; ++j1)
            {
                pos[0] = bitPosition[j1];
                for (int j2 = j1+1; !found && j2 < k; ++j2)
                {
                    pos[1] = bitPosition[j2];

                    ++nsteps;
                    pos[2] = 2*pos[1]-pos[0];
                    // calculate 3rd bit index that might be set;
                    // the other two indices point to bits that are set
                    if (pos[2] > lastBitSetPosition)
                        break;
                    // loop inner loop until we go out of bounds

                    found = this.bits.get(pos[2]);
                    // we're done if we find a third 1!
                }
            }
        }
        if (!found)
            pos[0]=-1;
        return nsteps;
    }

    /*
     * run an algorithm that finds evenly spaced ones and returns # of steps.
     */
    public TestResult run()
    {
        bernoulli.setProbability(pmin + (pmax-pmin)*random.nextDouble());
        // probability of bernoulli process is randomly distributed between pmin and pmax

        // generate bit string.
        int k = generateBits();
        int[] pos = new int[3];
        int nsteps = findEvenlySpacedOnes(k, pos);
        return new TestResult(k, nsteps); 
    }

    public static void main(String[] args)
    {
        int n;
        int ntrials;
        double pmin = 0, pmax = 1;
        try {
            n = Integer.parseInt(args[0]);
            ntrials = Integer.parseInt(args[1]);
            if (args.length >= 3)
                pmin = Double.parseDouble(args[2]);
            if (args.length >= 4)
                pmax = Double.parseDouble(args[3]);
        }
        catch (Exception e)
        {
            System.out.println("usage: EvenlySpacedOnesTest N NTRIALS [pmin [pmax]]");
            System.exit(0);
            return; // make the compiler happy
        }

        final StatisticalSummary[] statistics;
        statistics=new StatisticalSummary[n+1];
        for (int i = 0; i <= n; ++i)
        {
            statistics[i] = new StatisticalSummary();
        }

        EvenlySpacedOnesTest test = new EvenlySpacedOnesTest(n, ntrials, pmin, pmax);
        int printInterval=100000;
        int nextPrint = printInterval;
        for (int i = 0; i < ntrials; ++i)
        {
            TestResult result = test.run();
            statistics[result.k].add(result.nsteps);
            if (i == nextPrint)
            {
                System.err.println(i);
                nextPrint += printInterval;
            }
        }
        StatisticalSummary.printOut(System.out, statistics);
    }
}

0
# <algorithm>
def contains_evenly_spaced?(input)
  return false if input.size < 3
  one_indices = []
  input.each_with_index do |digit, index|
    next if digit == 0
    one_indices << index
  end
  return false if one_indices.size < 3
  previous_indexes = []
  one_indices.each do |index|
    if !previous_indexes.empty?
      previous_indexes.each do |previous_index|
        multiple = index - previous_index
        success_index = index + multiple
        return true if input[success_index] == 1
      end
    end
    previous_indexes << index
  end
  return false
end
# </algorithm>

def parse_input(input)
  input.chars.map { |c| c.to_i }
end

我遇到了数百万个数字的最坏情况。模糊化/dev/urandom本质上会给您O(n),但我知道最坏的情况比这更糟。我只是不知道有多糟。对于small n,在附近找到输入很简单3*n*log(n),但是对于这个特殊问题,很难将输入与其他增长顺序区分开。

从事最坏情况输入的任何人都可以生成长度大于十万的字符串吗?


正如我在回答中指出的那样,很容易生成任意数量的数字的(尽管不是最坏的情况)字符串:将1s恰好位于三元表示形式中不包含任何“ 1”的位置p(即位置2、6、8、18、20、24、26、54、56、60 ...:请参见research.att.com/~njas/sequences/…的公式)。对于3 ^ 13≈1百万,这具有2 ^ 13≈8000 1s。在这样的字符串上的运行时间将约为≈n ^(1.26)-对于这么小的n,可能仍然很难与O(n log n)区分开。试试看。
ShreevatsaR


-3

这可以解决吗?我不确定是否为O(nlogn),但我认为它比O(n²)好,因为唯一找不到三元组的方法是质数分布。

有改进的空间,第二个发现1可能是下一个第一个1。也没有错误检查。

#include <iostream>

#include <string>

int findIt(std::string toCheck) {
    for (int i=0; i<toCheck.length(); i++) {
        if (toCheck[i]=='1') {
            std::cout << i << ": " << toCheck[i];
            for (int j = i+1; j<toCheck.length(); j++) {
                if (toCheck[j]=='1' && toCheck[(i+2*(j-i))] == '1') {
                    std::cout << ", " << j << ":" << toCheck[j] << ", " << (i+2*(j-i)) << ":" << toCheck[(i+2*(j-i))] << "    found" << std::endl;
                    return 0;
                }
            }
        }
    }
    return -1;
}

int main (int agrc, char* args[]) {
    std::string toCheck("1001011");
    findIt(toCheck);
    std::cin.get();
    return 0;
}

1
从技术上讲,这是O(n ^ 2)。平均而言,每次运行时,内部循环将迭代n的一半以上。因此它可以写为O(n *(n / 2)),可以简化为O(n ^ 2)
罗伯特·帕克2009年

嗯,看来您是对的。这不是一个简单的问题,只是找到所有1个取O(n),没有任何空间进行进一步的搜索/比较(O(logn)复杂度)。
2009年

-3

我认为该算法具有O(n log n)复杂度(C ++,DevStudio 2k5)。现在,我不知道如何分析算法以确定算法复杂性的细节,因此我在代码中添加了一些度量收集信息。对于任何给定的输入,该代码都会对在1和0的序列上完成的测试次数进行计数(希望我没有做过很多算法)。我们可以将实际测试次数与O值进行比较,看看是否存在相关性。

#include <iostream>
using namespace std;

bool HasEvenBits (string &sequence, int &num_compares)
{
  bool
    has_even_bits = false;

  num_compares = 0;

  for (unsigned i = 1 ; i <= (sequence.length () - 1) / 2 ; ++i)
  {
    for (unsigned j = 0 ; j < sequence.length () - 2 * i ; ++j)
    {
      ++num_compares;
      if (sequence [j] == '1' && sequence [j + i] == '1' && sequence [j + i * 2] == '1')
      {
        has_even_bits = true;
        // we could 'break' here, but I want to know the worst case scenario so keep going to the end
      }
    }
  }

  return has_even_bits;
}

int main ()
{
  int
    count;

  string
    input = "111";

  for (int i = 3 ; i < 32 ; ++i)
  {
    HasEvenBits (input, count);
    cout << i << ", " << count << endl;
    input += "0";
  }
}

该程序输出每个字符串长度最多32个字符的测试次数。结果如下:

 n  Tests  n log (n)
=====================
 3     1     1.43
 4     2     2.41
 5     4     3.49
 6     6     4.67
 7     9     5.92
 8    12     7.22
 9    16     8.59
10    20    10.00
11    25    11.46
12    30    12.95
13    36    14.48
14    42    16.05
15    49    17.64
16    56    19.27
17    64    20.92
18    72    22.59
19    81    24.30
20    90    26.02
21   100    27.77
22   110    29.53
23   121    31.32
24   132    33.13
25   144    34.95
26   156    36.79
27   169    38.65
28   182    40.52
29   196    42.41
30   210    44.31
31   225    46.23

我还添加了'n log n'值。使用您选择的绘图工具绘制这些图形,以查看两个结果之间的相关性。此分析是否扩展到所有n值?我不知道。


我同意,这不是完美的关联。但是,该曲线比n ^ 2更接近n log n。
Skizz

3
尝试将输入大小增加到一百万或更多。在小输入的情况下,曲线通常看起来与算法的曲线相似,当加大输入大小时,显然更好。
尼克·拉森

内部为外部的外部为边界的double for循环为三角形,其复杂度仍为O(n ^ 2)。考虑所有(i,j),使得[0,n]中的i和[0,n-2 * i]中的j,您有一个三角形,并且三角形的面积具有二次趋势。
Matthieu M.

确切地说,对于偶数n,测试=(n ^ 2-2n)/ 4;显然是二次的。
爷爷
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.