8

Given an array of size 3n of the form

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

Convert it to [x1, y1, z1, x2, y2, z2, ... xn, yn, zn]

Here xn, yn, zn can be any integers. See example input and output below.

Two constraints

  1. Do in O(n)
  2. O(1) memory (inplace)

An example input and output are as follows.

Input :
[5, 8, 11, 3, 2, 17, 21, 1, 9] 3n = 9. So n = 3.

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

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

One possible O(n log n) soln: Considering just x's and y's. Now I can swap all y's to its position which will leave me x2, x4, x6 swapped out of position. Then I will swap in x2, x4's which will leave x3, x7's out of position. And next iteration would be x8, x16's. This would take me to O(n log n) but not O(n).

Niklas B.
  • 92,950
  • 18
  • 194
  • 224
Mohan
  • 1,850
  • 1
  • 19
  • 42
  • I did do research. I have O(nlogn) solution inplace but couldn't go further beyond than that – Mohan May 07 '14 at 20:08
  • @AlexandruSeverin I think you got confused by the fact x1, x2, ... x's y's and z's can be any random number. And it is not related to sorting – Mohan May 07 '14 at 20:20
  • 1
    Perhaps a duplicate (albeit with somewhat bad answers) - [how to swap array-elements to transfer the array from a column-like into a row-like representation](http://stackoverflow.com/q/3009379). Also [on CareerCup](http://www.careercup.com/question?id=13573674). (Surprisingly easy to Google if you change it to `x,y,z` to `a,b,c`). – Bernhard Barker May 07 '14 at 20:36
  • @Dukeling Definitely related, but the general problem might be a lot harder than the Nx3 one. In particular the cycle sequence depends on N and M and might be simpler to implement for M=3 than for arbitrary M – Niklas B. May 07 '14 at 20:38
  • Also on [Joel's discussion forum](http://discuss.joelonsoftware.com/default.asp?interview.11.810768.13). – Bernhard Barker May 07 '14 at 20:42
  • @DanielBrückner Not my downvote, but it's a somewhat uninteresting twist on a question that has been asked before many times. – David Eisenstat May 07 '14 at 20:47
  • @DavidEisenstat Well then we should close as duplicate, if we find a link – Niklas B. May 07 '14 at 20:47
  • If you know `n`, it's just arithmetic to map the array indexes and only touch each location once or a simple nested loop (one dimension is fixed, and so doesn't change the complexity). If you don't know `n`, you can presumably determine it in O(n) time, which doesn't alter the complexity. – John C May 07 '14 at 20:48
  • @NiklasB. It's not an exact dupe because it fixes k = 3 rather than setting it to 2 or leaving it variable. – David Eisenstat May 07 '14 at 20:48
  • @DavidEisenstat Maybe that makes it easier, because O(n * f(k)) is then O(n) – Niklas B. May 07 '14 at 20:49
  • @NiklasB. Yes, it makes things simpler. I know that there's an algorithm, based on modifying [this paper](http://arxiv.org/pdf/0805.1598v1.pdf) slightly, but I don't think that I'm going to write it up. – David Eisenstat May 07 '14 at 20:50
  • I wasn't aware that there is a well known algorithm. Well, figuring it out was fun nonetheless. – Daniel Brückner May 07 '14 at 20:58
  • Inplace matrix transposition is the name I am looking for then... – Mohan May 07 '14 at 20:59
  • Seems like no algorithm for `O(n)` time and `O(1)` memory is known, at least in the general case. Not sure if it helps that one dimension is 3. Does your array only contain positive numbers? – Daniel Brückner May 07 '14 at 21:11
  • Sorry forgot to mention. Yes it contains only positive numbers. – Mohan May 07 '14 at 21:22
  • Then you can "borrow" the sign bit and run the cycle-leader algorithm on the other end of the Wikipedia links. – David Eisenstat May 07 '14 at 21:24
  • @DavidEisenstat Unless they're unsigned integers :) – Niklas B. May 07 '14 at 21:26
  • That is what I wanted to suggest, too, assuming the data type is signed integer. If the numbers a guaranteed to be small, i.e. at least the highest bit is always zero, you can use the idea, too. – Daniel Brückner May 07 '14 at 21:26

2 Answers2

3

This answer is based on work by Peiyush Jain (whose bibliography is woefully incomplete, but I don't feel like taking the time to straighten out the history of the in-place transposition problem). Observe that 3 is a primitive root of 25 = 5^2, since

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

and 20 is Euler's totient of 25. By Jain's Theorem 1, a classic result in number theory, 3 is a primitive root for all 5^k.

When the array has length 3n, the new position of the element at position k*n + j is 3*j + k. In general, the new position of i (except for the last element) is (i*n) % (3*n - 1). Note that n is the multiplicative inverse of 3 modulo 3*n - 1, so 3 is a primitive root if and only if n is.

Jain's observation, in this case, is that, if 3*n - 1 is a power of 5, then the permutation above has log_5 (3*n - 1) + 1 distinct cycles, led by 5^k for k from 0 to log_5 (3*n - 1). (This is more or less the definition of primitive root.) For each cycle, all we have to do is move the leader, move the element displaced by the leader, move the element displaced by the element displaced by the leader, etc., until we return to the leader.

For other array sizes, break the array into O(log n) implicit subarrays of lengths 3 and one plus powers of 5 that are divisible by 3: 6, 126, 3126, 78126, etc. Do a series of rotations, decreasing geometrically in size, to get the subarrays contiguous, then run the above algorithm.

If you actually implement this, please benchmark it. I did for the base case of Jain's algorithm (3^n - 1, pairs instead of triples) and found that, on my machine the O(n log n)-time algorithm was faster for non-galactic input sizes. YMMV of course.

David Eisenstat
  • 64,237
  • 7
  • 60
  • 120
  • I tested with two sizes one with 1000 and another with 10M. And you are correct, for 1000 the O(n log n) was faster. – Mohan May 08 '14 at 18:13
0

Since David does not seem interested in writing it down (well obviously he is interested, see the other answer :), I will use his reference to arrive at an algorithm for the case with 3 partitions.

First note that if we can solve the problem efficiently for some m < n using an algorithm A, we can rearrange the array so that we can apply A and are then left with a smaller subproblem. Say the original array is

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

We want to rearrange it to

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

This is basically a transformation of the pattern AaBbCc to ABCabc where A, B, C and a, b, c have the same lengths, respectively. We can achieve that through a series of reversals. Let X' denote the reversal of string X here:

   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

There's probably a shorter way, but this is still just a constant number of operations, so it takes only linear time. One could use a more sophisticated algorithm here to implement some of the cyclic shifts, but that's just an optimization.

Now we can solve the two partitions of our array recursively and we're done.

The question remains, what would be a nice m that allows us to solve the left part easily?

To figure this out, we need to realize that what we want to implement is a particular permutation P of the array indices. Every permutation can be decomposed into a set of cycles a0 -> a1 -> ... -> a{k-1} -> a0, for which we have P(ai) = a{(i + 1) % k}. It is easy to process such a cycle in-place, the algorithm is outlined on Wikipedia.

Now the problem is that after you completed processing one of the cycle, to find an element that is part of a cycle you have not yet processed. There is no generic solution for this, but for some particular permutations there are nice formulas that describe what exactly the positions are that are part of the different cycles.

For your problems, you just choose m = (5^(2k) - 1)/3, such that m < n and k is maximum. A sequence of elements that are part of all the different cycles is 5^0, 5^1, ..., 5^{k-1}. You can use those to implement the cycle-leader algorithm on the left part of the array (after the shifting) in O(m).

We solve the leftover right part recursively and get an algorithm to solve the problem in time

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

and since m >= Omega(n), we get T(n) = O(n).

Niklas B.
  • 92,950
  • 18
  • 194
  • 224