在O(n)时间内在阵列中交错三个大小相等的分区

时间:2014-05-07 20:00:08

标签: algorithm

给出一个大小为3n的数组

[x1, x2, x3... xn, y1, y2, y3... yn, z1, z2, z3... zn]

将其转换为[x1, y1, z1, x2, y2, z2, ... xn, yn, zn]

这里xn,yn,zn可以是任何整数。请参阅下面的示例输入和输出。

两个约束

  1. 做O(n)
  2. O(1)记忆(就地)
  3. 示例输入和输出如下。

    输入:
    [5, 8, 11, 3, 2, 17, 21, 1, 9] 3n = 9.所以n = 3。

    下面 x1=5 x2=8 x3=11 y1=3 y2=2 y3=17 z1=21 z2=1 z3=9

    输出:
    [5, 3, 21, 8, 2, 1, 11, 17, 9]

    一个可能的O(n log n)soln: 只考虑x和y。现在我可以将所有y换成它的位置,这将使我x2,x4,x6换出位置。然后我将交换x2,x4,这将使x3,x7离开位置。下一次迭代将是x8,x16。这将带我到O(n log n)但不是O(n)。

2 个答案:

答案 0 :(得分:3)

这个答案是基于Peiyush Jain的工作(其参考书目非常不完整,但我不想花时间理顺就地换位问题的历史)。观察到3是25 = 5 ^ 2的原始根,因为

>>> len(set(pow(3,n,25)for n in range(25)))
20

和20是欧拉的25个算子。根据Jain的定理1,数论中的经典结果,3是所有5 ^ k的原始根。

当数组长度为3n时,位置k * n + j处元素的新位置为3 * j + k。通常,i的新位置(除了最后一个元素)是(i * n)%(3 * n - 1)。注意,n是3模3 * n - 1的乘法逆,所以3是原始根,当且仅当n是。

在这种情况下,耆那教的观察结果是,如果3 * n - 1是5的幂,则上面的置换有log_5(3 * n - 1)+ 1个不同的周期,由5引导^ k表示从0到log_5(3 * n - 1)的k。 (这或多或少是原始根的定义。)对于每个循环,我们所要做的就是移动领导者,移动被领导者移位的元素,移动被领导者移位的元素移位的元素等,直到我们回到领导者那里。

对于其他数组大小,将数组分解为长度为3的O(log n)隐式子数组,以及可被3:6,126,3126,78126等整除的1加上5的幂。执行一系列旋转,减小几何尺寸,使子阵列连续,然后运行上述算法。

如果您实际执行此操作,请对其进行基准测试。我做了Jain算法的基本情况(3 ^ n - 1,成对而不是三元组)并且发现,在我的机器上, O(n log n)-time算法对于非银河输入大小。 YMMV当然。

答案 1 :(得分:0)

由于大卫似乎没有兴趣写下来(显然他 感兴趣,请参阅另一个答案:),我将使用his reference来获得案例算法有3个分区。

首先请注意,如果我们能够有效地解决某些 m< n 使用算法 A ,我们可以重新排列数组,以便我们可以应用 A ,然后留下一个较小的子问题。假设原始数组是

x1 .. xm x{m+1}.. xn y1 .. ym y{m+1} .. yn z1 .. zm z{m+1} .. zn

我们想将其重新排列为

x1 .. xm y1 .. ym z1 .. zm x{m+1} .. xn y{m+1} .. yn z{m+1} .. zn

这基本上是模式AaBbCcABCabc的转换,其中A,B,C和a,b,c分别具有相同的长度。我们可以通过一系列逆转来实现这一目标。让X'表示字符串X的反转:

   AaBbCc
-> Aa(BbCc)' = Aac'C'b'B'
-> Aac'(C'b')'B' = Aac'bCB'
-> A(ac'bCB')' = ABC'b'ca'
-> ABCb'ca'
-> ABC(b'ca')' = ABCac'b
-> ABCa(c'b)' = ABCab'c
-> ABCabc

这可能是一种较短的方式,但这只是一个恒定的操作次数,所以它只需要线性时间。可以在这里使用更复杂的算法来实现一些循环移位,但这只是一种优化。

现在我们可以递归地解决数组的两个分区,我们已经完成了。

问题仍然存在,什么是一个很好的m,可以让我们轻松解决左边的部分?

为了解决这个问题,我们需要意识到我们想要实现的是数组索引的特定permutation P.每个排列can be decomposed into a set of cycles a0 -> a1 -> ... -> a{k-1} -> a0,我们有P(ai)= a {(i + 1)%k}。很容易就地处理这样的循环,算法概述on Wikipedia

现在的问题是,在完成一个循环的处理之后,找到一个尚未处理的循环元素。对此没有通用的解决方案,但是对于某些特定的排列,有很好的公式可以描述不同周期中的位置究竟是什么。

对于你的问题,你只需选择m =(5 ^(2k) - 1)/ 3,这样m< n和k是最大值。作为所有不同周期的一部分的元素序列是5 ^ 0,5 ^ 1,...,5 ^ {k-1}。您可以使用它们在O(m)的数组左侧(移位后)实现循环引导算法。

我们递归地解决剩余的右边部分并获得一个算法来及时解决问题

T(n) = O(m) + T(n - m)

由于m> = Omega(n),我们得到T(n)= O(n)。