Вопрос

I saw this question is a programming interview book, here I'm simplifying the question.

Assume you have an array A of length n, and you have a permutation array P of length n as well. Your method will return an array where elements of A will appear in the order with indices specified in P.

Quick example: Your method takes A = [a, b, c, d, e] and P = [4, 3, 2, 0, 1]. then it will return [e, d, c, a, b]. You are allowed to use only constant space (i.e. you can't allocate another array, which takes O(n) space).

Ideas?

Это было полезно?

Решение 3

There is a trivial O(n^2) algorithm, but you can do this in O(n). E.g.:

A = [a, b, c, d, e]

P = [4, 3, 2, 0, 1]

We can swap each element in A with the right element required by P, after each swap, there will be one more element in the right position, and do this in a circular fashion for each of the positions (swap elements pointed with ^s):

[a, b, c, d, e] <- P[0] = 4 != 0 (where a initially was), swap 0 (where a is) with 4
 ^           ^
[e, b, c, d, a] <- P[4] = 1 != 0 (where a initially was), swap 4 (where a is) with 1
    ^        ^
[e, a, c, d, b] <- P[1] = 3 != 0 (where a initially was), swap 1 (where a is) with 3
    ^     ^
[e, d, c, a, b] <- P[3] = 0 == 0 (where a initially was), finish step

After one circle, we find the next element in the array that does not stay in the right position, and do this again. So in the end you will get the result you want, and since each position is touched a constant time (for each position, at most one operation (swap) is performed), it is O(n) time.

You can stored the information of which one is in its right place by:

  1. set the corresponding entry in P to -1, which is unrecoverable: after the operations above, P will become [-1, -1, 2, -1, -1], which denotes that only the second one might be not in the right position, and a further step will make sure it is in the right position and terminates the algorithm;

  2. set the corresponding entry in P to -n - 1: P becomes [-5, -4, 2, -1, -2], which can be recovered in O(n) trivially.

Другие советы

Yet another unnecessary answer! This one preserves the permutation array P explicitly, which was necessary for my situation, but sacrifices in cost. Also this does not require tracking the correctly placed elements. I understand that a previous answer provides the O(N) solution, so I guess this one is just for amusement!

We get best case complexity O(N), worst case O(N^2), and average case O(NlogN). For large arrays (N~10000 or greater), the average case is essentially O(N).

Here is the core algorithm in Java (I mean pseudo-code *cough cough*)

int ind=0;
float temp=0;

for(int i=0; i<(n-1); i++){
  // get next index
  ind = P[i];
  while(ind<i)
    ind = P[ind];

  // swap elements in array
  temp = A[i];
  A[i] = A[ind];
  A[ind] = temp;
}

Here is an example of the algorithm running (similar to previous answers):

let A = [a, b, c, d, e]

and P = [2, 4, 3, 0, 1]

then expected = [c, e, d, a, b]

i=0:  [a, b, c, d, e] // (ind=P[0]=2)>=0 no while loop, swap A[0]<->A[2]
       ^     ^
i=1:  [c, b, a, d, e] // (ind=P[1]=4)>=1 no while loop, swap A[1]<->A[4]
          ^        ^
i=2:  [c, e, a, d, b] // (ind=P[2]=3)>=2 no while loop, swap A[2]<->A[3]
             ^  ^
i=3a: [c, e, d, a, b] // (ind=P[3]=0)<3 uh-oh! enter while loop...
                ^
i=3b: [c, e, d, a, b] // loop iteration: ind<-P[0]. now have (ind=2)<3
       ?        ^
i=3c: [c, e, d, a, b] // loop iteration: ind<-P[2]. now have (ind=3)>=3
             ?  ^
i=3d: [c, e, d, a, b] // good index found. Swap A[3]<->A[3]
                ^
done.

This algorithm can bounce around in that while loop for any indices j<i, up to at most i times during the ith iteration. In the worst case (I think!) each iteration of the outer for loop would result in i extra assignments from the while loop, so we'd have an arithmetic series thing going on, which would add an N^2 factor to the complexity! Running this for a range of N and averaging the number of 'extra' assignments needed by the while loop (averaged over many permutations for each N, that is), though, strongly suggests to me that the average case is O(NlogN).

Thanks!

@RinRisson has given the only completely correct answer so far! Every other answer has been something that required extra storage — O(n) stack space, or assuming that the permutation P was conveniently stored adjacent to O(n) unused-but-mutable sign bits, or whatever.

Here's RinRisson's correct answer written out in C++. This passes every test I have thrown at it, including an exhaustive test of every possible permutation of length 0 through 11.

Notice that you don't even need the permutation to be materialized; we can treat it as a completely black-box function OldIndex -> NewIndex:

template<class RandomIt, class F>
void permute(RandomIt first, RandomIt last, const F& p)
{
    using IndexType = std::decay_t<decltype(p(0))>;

    IndexType n = last - first;
    for (IndexType i = 0; i + 1 < n; ++i) {
        IndexType ind = p(i);
        while (ind < i) {
            ind = p(ind);
        }
        using std::swap;
        swap(*(first + i), *(first + ind));
    }
}

Or slap a more STL-ish interface on top:

template<class RandomIt, class ForwardIt>
void permute(RandomIt first, RandomIt last, ForwardIt pfirst, ForwardIt plast)
{
    assert(std::distance(first, last) == std::distance(pfirst, plast));
    permute(first, last, [&](auto i) { return *std::next(pfirst, i); });
}

The simplest case is when there is only a single swap for an element to the destination index. for ex: array=abcd perm =1032. you just need two direct swaps: ab swap, cd swap

for other cases, we need to keep swapping until an element reaches its final destination. for ex: abcd, 3021 starting with first element, we swap a and d. we check if a's destination is 0 at perm[perm[0]]. its not, so we swap a with elem at array[perm[perm[0]]] which is b. again we check if a's has reached its destination at perm[perm[perm[0]]] and yes it is. so we stop.

we repeat this for each array index. Every item is moved in-place only once, so it's O(N) with O(1) storage.

def permute(array, perm):

for i in range(len(array)):
    elem, p = array[i], perm[i]

    while( p != i ): 
        elem, array[p] = array[p], elem  
        elem = array[p]
        p = perm[p]

return array

I agree with many solutions here, but below is a very short code snippet that permute throughout a permutation cycle:

def _swap(a, i, j):
    a[i], a[j] = a[j], a[i]


def apply_permutation(a, p):
    idx = 0
    while p[idx] != 0:
        _swap(a, idx, p[idx])
        idx = p[idx]

So the code snippet below

a = list(range(4))
p = [1, 3, 2, 0]
apply_permutation(a, p)
print(a)

Outputs [2, 4, 3, 1]

Just a simple example C/C++ code addition to the Ziyao Wei's answer. Code is not allowed in comments, so as an answer, sorry:

for (int i = 0; i < count; ++i)
{
    // Skip to the next non-processed item
    if (destinations[i] < 0)
        continue;

    int currentPosition = i;

    // destinations[X] = Y means "an item on position Y should be at position X"
    // So we should move an item that is now at position X somewhere
    // else - swap it with item on position Y. Then we have a right
    // item on position X, but the original X-item now on position Y,
    // maybe should be occupied by someone else (an item Z). So we
    // check destinations[Y] = Z and move the X-item further until we got
    // destinations[?] = X which mean that on position ? should be an item
    // from position X - which is exactly the X-item we've been kicking
    // around all this time. Loop closed.
    // 
    // Each permutation has one or more such loops, they obvisouly
    // don't intersect, so we may mark each processed position as such
    // and once the loop is over go further down by an array from
    // position X searching for a non-marked item to start a new loop.
    while (destinations[currentPosition] != i)
    {
        const int target = destinations[currentPosition];

        std::swap(items[currentPosition], items[target]);
        destinations[currentPosition] = -1 - target;

        currentPosition = target;
    }

    // Mark last current position as swapped before moving on
    destinations[currentPosition] = -1 - destinations[currentPosition];
}

for (int i = 0; i < count; ++i)
    destinations[i] = -1 - destinations[i];

(for C - replace std::swap with something else)

You can consequently put the desired element to the front of the array, while working with the remaining array of the size (n-1) in the the next iteration step.

The permutation array needs to be accordingly adjusted to reflect the decreasing size of the array. Namely, if the element you placed in the front was found at position "X" you need to decrease by one all the indexes greater or equal to X in the permutation table.

In the case of your example:

array                   permutation -> adjusted permutation

A  =  {[a  b  c  d  e]}                 [4 3 2 0 1]
A1 =  { e [a  b  c  d]}   [3 2 0 1] ->    [3 2 0 1] (decrease all indexes >= 4)
A2 =  { e  d [a  b  c]}     [2 0 1] ->      [2 0 1] (decrease all indexes >= 3)
A3 =  { e  d  c [a  b]}       [0 1] ->        [0 1] (decrease all indexes >= 2)
A4 =  { e  d  c  a [b]}         [1] ->          [0] (decrease all indexes >= 0)

Another example:

A0 = {[a  b  c  d  e]}                  [0 2 4 3 1]
A1 = { a [b  c  d  e]}     [2 4 3 1] ->   [1 3 2 0] (decrease all indexes >= 0)
A2 = { a  c [b  d  e]}       [3 2 0] ->     [2 1 0] (decrease all indexes >= 2)
A3 = { a  c  e [b  d]}         [1 0] ->       [1 0] (decrease all indexes >= 2)
A4 = { a  c  e  d [b]}           [0] ->         [0] (decrease all indexes >= 1)

The algorithm, though not the fastest, avoids the extra memory allocation while still keeping the track of the initial order of elements.

Here a clearer version which takes a swapElements function that accepts indices, e.g., std::swap(Item[cycle], Item[P[cycle]])$ Essentially it runs through all elements and follows the cycles if they haven't been visited yet. Instead of the second check !visited[P[cycle]], we could also compare with the first element in the cycle which has been done somewhere else above.

 bool visited[n] = {0};
 for (int i = 0; i < n; i++)   {
     int cycle = i;
     while(! visited[cycle] && ! visited[P[cycle]]) {
         swapElements(cycle,P[cycle]);
         visited[cycle]=true;
         cycle = P[cycle];
     }
 }

Traceback what we have swapped by checking index.

Java, O(N) swaps, O(1) space:

    static void swap(char[] arr, int x, int y) {
        char tmp = arr[x];
        arr[x] = arr[y];
        arr[y] = tmp;
    }
    public static void main(String[] args) {
        int[] intArray = new int[]{4,2,3,0,1};
        char[] charArray = new char[]{'A','B','C','D','E'};
        for(int i=0; i<intArray.length; i++) {
            int index_to_swap = intArray[i];
            // Check index if it has already been swapped before
            while (index_to_swap < i) {
                // trace back the index
                index_to_swap = intArray[index_to_swap];
            }
            swap(charArray, index_to_swap, i);
        }
    }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top