40

Input: A read-only array of N elements containing integer values from 1 to N (some integer values can appear more than once!). And a memory zone of a fixed size (10, 100, 1000 etc - not depending on N).

How to tell in O(n) if the array represents a permutation?

--What I achieved so far (an answer proved that this was not good):--

  1. I use the limited memory area to store the sum and the product of the array.
  2. I compare the sum with N*(N+1)/2 and the product with N!

I know that if condition (2) is true I might have a permutation. I'm wondering if there's a way to prove that condition (2) is sufficient to tell if I have a permutation. So far I haven't figured this out ...

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
INS
  • 10,594
  • 7
  • 58
  • 89
  • 3
    no, it is purely for fun – INS May 20 '10 at 10:54
  • 3
    The storage required for the product `N!`, strictly speaking, depends on `N`. And strictly speaking, you can't multiply `N` numbers in `O(N)`. – polygenelubricants May 20 '10 at 11:01
  • 1
    I believe this would be a solution: http://aperiodic.net/phil/archives/Geekery/find-duplicate-elements.html – INS May 20 '10 at 11:03
  • 3
    Almost duplicate: http://stackoverflow.com/questions/177118/algorithm-to-determine-if-array-contains-n-nm – Eric Bainville May 20 '10 at 11:14
  • @Iulian: The article you linked doesn't solve this problem: It makes the assumption that the array does not contain the value N. – interjay May 20 '10 at 11:27
  • Here is another reason this can't be solved: Say you have processed `m` out of `n` numbers, and stop your algorithm. Now you can (though it takes some time) decide which `m` numbers you have seen, by processing any stream of `n-m` numbers and seeing when you have a permutation. So from the perspective of information, you have stored all the numbers `m` you've seen, and so must use linear memory. – Thomas Ahle Apr 26 '16 at 14:10

16 Answers16

16

I'm very slightly skeptical that there is a solution. Your problem seems to be very close to one posed several years ago in the mathematical literature, with a summary given here ("The Duplicate Detection Problem", S. Kamal Abdali, 2003) that uses cycle-detection -- the idea being the following:

If there is a duplicate, there exists a number j between 1 and N such that the following would lead to an infinite loop:

x := j;
do
{
   x := a[x];
}
while (x != j);

because a permutation consists of one or more subsets S of distinct elements s0, s1, ... sk-1 where sj = a[sj-1] for all j between 1 and k-1, and s0 = a[sk-1], so all elements are involved in cycles -- one of the duplicates would not be part of such a subset.

e.g. if the array = [2, 1, 4, 6, 8, 7, 9, 3, 8]

then the element in bold at position 5 is a duplicate because all the other elements form cycles: { 2 -> 1, 4 -> 6 -> 7 -> 9 -> 8 -> 3}. Whereas the arrays [2, 1, 4, 6, 5, 7, 9, 3, 8] and [2, 1, 4, 6, 3, 7, 9, 5, 8] are valid permutations (with cycles { 2 -> 1, 4 -> 6 -> 7 -> 9 -> 8 -> 3, 5 } and { 2 -> 1, 4 -> 6 -> 7 -> 9 -> 8 -> 5 -> 3 } respectively).

Abdali goes into a way of finding duplicates. Basically the following algorithm (using Floyd's cycle-finding algorithm) works if you happen across one of the duplicates in question:

function is_duplicate(a, N, j)
{
     /* assume we've already scanned the array to make sure all elements
        are integers between 1 and N */
     x1 := j;
     x2 := j;
     do
     {             
         x1 := a[x1];
         x2 := a[x2];
         x2 := a[x2];
     } while (x1 != x2);

     /* stops when it finds a cycle; x2 has gone around it twice, 
        x1 has gone around it once.
        If j is part of that cycle, both will be equal to j. */
     return (x1 != j);
}

The difficulty is I'm not sure your problem as stated matches the one in his paper, and I'm also not sure if the method he describes runs in O(N) or uses a fixed amount of space. A potential counterexample is the following array:

[3, 4, 5, 6, 7, 8, 9, 10, ... N-10, N-9, N-8, N-7, N-2, N-5, N-5, N-3, N-5, N-1, N, 1, 2]

which is basically the identity permutation shifted by 2, with the elements [N-6, N-4, and N-2] replaced by [N-2, N-5, N-5]. This has the correct sum (not the correct product, but I reject taking the product as a possible detection method since the space requirements for computing N! with arbitrary precision arithmetic are O(N) which violates the spirit of the "fixed memory space" requirement), and if you try to find cycles, you will get cycles { 3 -> 5 -> 7 -> 9 -> ... N-7 -> N-5 -> N-1 } and { 4 -> 6 -> 8 -> ... N-10 -> N-8 -> N-2 -> N -> 2}. The problem is that there could be up to N cycles, (identity permutation has N cycles) each taking up to O(N) to find a duplicate, and you have to keep track somehow of which cycles have been traced and which have not. I'm skeptical that it is possible to do this in a fixed amount of space. But maybe it is.

This is a heavy enough problem that it's worth asking on mathoverflow.net (despite the fact that most of the time mathoverflow.net is cited on stackoverflow it's for problems which are too easy)


edit: I did ask on mathoverflow, there's some interesting discussion there.

Community
  • 1
  • 1
Jason S
  • 184,598
  • 164
  • 608
  • 970
  • This algorithm in the paper requires an array of size n+1, so that it always contains at least one duplicate. This is not the same problem as the OP. Perhaps the algorithm can be adapted, but it cannot be used verbatim. – Jules May 20 '10 at 16:15
  • shouldn't the return condition of `is_duplicate(a,N,j)` be `return (x1==j)` if the function was supposed to return `true` when `j` is duplicate. – dark_prince Jul 28 '21 at 01:19
10

This is impossible to do in O(1) space, at least with a single-scan algorithm.

Proof

Suppose you have processed N/2 of the N elements. Assuming the sequence is a permutation then, given the state of the algorithm, you should be able to figure out the set of N/2 remaining elements. If you can't figure out the remaining elements, then the algorithm can be fooled by repeating some of the old elements.

There are N choose N/2 possible remaining sets. Each of them must be represented by a distinct internal state of the algorithm, because otherwise you couldn't figure out the remaining elements. However, it takes logarithmic space to store X states, so it takes BigTheta(log(N choose N/2)) space to store N choose N/2 states. That values grows with N, and therefore the algorithm's internal state can not fit in O(1) space.

More Formal Proof

You want to create a program P which, given the final N/2 elements and the internal state of the linear-time-constant-space algorithm after it has processed N/2 elements, determines if the entire sequence is a permutation of 1..N. There is no time or space bound on this secondary program.

Assuming P exists we can create a program Q, taking only the internal state of the linear-time-constant-space algorithm, which determines the necessary final N/2 elements of the sequence (if it was a permutation). Q works by passing P every possible final N/2 elements and returning the set for which P returns true.

However, because Q has N choose N/2 possible outputs, it must have at least N choose N/2 possible inputs. That means the internal state of the original algorithm must store at least N choose N/2 states, requiring BigTheta(log N choose N/2), which is greater than constant size.

Therefore the original algorithm, which does have time and space bounds, also can't work correctly if it has constant-size internal state.

[I think this idea can be generalized, but thinking isn't proving.]

Consequences

BigTheta(log(N choose N/2)) is equal to BigTheta(N). Therefore just using a boolean array and ticking values as you encounter them is (probably) space-optimal, and time-optimal too since it takes linear time.

Craig Gidney
  • 17,763
  • 5
  • 68
  • 136
  • I disagree with your approach. The phrases "you should be able to figure out the set of N/2 remaining elements" and "the algorithm can be fooled by repeating some of the old elements." are vague... if by the former you mean producing a set of the N/2 remaining elements, that is not a requirement of the problem. – Jason S May 20 '10 at 14:45
  • Why should you be able to figure out the set of N/2 remaining elements? All you need to say is that you have membership in the set of permutations (at the end) within the set of {1..N}^N. – Rex Kerr May 20 '10 at 14:47
  • What I meant is that, given the internal state of the algorithm, a program without bounded time and space must be able to determine the final N/2 elements. Equivalently, some program given the internal state and the final N/2 elements of the sequence must be able to determine if the entire sequence forms a permutation. [I removed the bounds to create that equivalence.] If an unbounded program can't do it when given the constant-sized internal state, then clearly the original bounded program also can't do it. – Craig Gidney May 20 '10 at 14:57
  • @JasonS I clarified the post. – Craig Gidney May 20 '10 at 15:12
  • 2
    You have proven that the problem is _not subdividable_, but not that it can't be solved in `O(N)` time. How do you know that there exists no strategy where at `N/2` of the way through the list, you might still need to revisit the earlier part of the list to process the rest? As long as you do it rarely enough, it could still be `O(N)`. – Rex Kerr May 20 '10 at 15:20
  • [NitPick: I'm not trying to prove it's not linear time. That would be silly.] It's true that the proof doesn't naively generalize to non-scanning algorithms, but it does generalize. First, if the algorithm is doing any unnecessary list accesses, those must be removed. It must only read a list item if it requires it for the result. Second, instead of considering the final N/2 items, you consider the last N/2 items which were accessed. So, if the algorithm finishes by reading all the odd elements, P is given the odd items and the algorithm's state just before it would have started reading them. – Craig Gidney May 20 '10 at 15:34
  • @RexKerr If I understand what you mean by 'set of {1..N]^N', then you're wrong. {1..N}^N contains sequences which aren't permutations of 1..N, such as 1 repeated N times. [Referring to your first comment] – Craig Gidney May 20 '10 at 15:55
  • @Strilanc - The generalization doesn't work, because your last `N/2` items have to be the same the last time you visit them as the first time (and all other times). Thus, depending on what's already happened, you may not have all `C(N,N/2)` possibilities open to you. And I just meant that "you need to show that you live in a small set of size `N!` that is a subset of a larger set of size `N^N` in which you can trivially determine membership. But you've fixed this in your proof (which I like, incidentally--I just think it only covers a subset of algorithms). – Rex Kerr May 20 '10 at 16:01
  • @RexKerr I agree that the proof, as stated, only applies to a subset of programs. I also agree that not all possibilities may be open when there are N/2 items left to read. I don't know how to properly generalize the proof, but I think it can be done. I feel that if you use state to store consistency information about the last N/2 items, you've wasted state you could have used to help P. P already knows the last N/2 items; it needs help with the other N/2. – Craig Gidney May 20 '10 at 18:13
  • Alright, there are definitely some algorithms that can do it in O(1) space. For example, doing the naive N^2 search for duplicate. The proof definitely doesn't generalize. – Craig Gidney May 20 '10 at 23:16
  • The problem clearly cannot be solved in a single pass with finite space. I don't think it can be solved without writing to the array. The interesting question is whether it can be solved in linear time, without any spare space, where one is allowed to write numbers to the array, but (1) the original array contents must be restored before the algorithm finishes, and (2) the maximum number that can be written to an array element is O(N). – supercat Oct 30 '10 at 18:39
  • @supercat That's easy. Multiply each value by 2, giving you n bitflags like A[0] & 1, and run the naive algorithm. Undo the multiplications after. – Craig Gidney Nov 15 '10 at 13:26
  • @Strilanc: You're correct that 2N is O(N). I should have written the latter requirement as "the maximum value that can be written to any array element is N+O(1)." Though perhaps one could have a non-constant number of sentinel values and still use a constant amount of extra space. – supercat Nov 16 '10 at 15:57
  • Your 'proof' would hold for a program that determines if the sum of all elements is a given value, and the claim is equally invalid there as here - being able to determine if a permutation is valid does not imply you could generate the permutation - in the sum example, you can determine validity, but only know what the sum of the remaining elements would be. – Nick Johnson Jul 25 '11 at 03:13
  • @NickJohnson If the items are bounded by a polynomial of the input size, then only O(log N) space is required to store the partial sums compared to O(N) for the partial sets. – Craig Gidney Jul 25 '11 at 11:03
  • @Strilanc But that makes the (unproven) assumption that partial sets are required for any algorithm that implements this. – Nick Johnson Jul 26 '11 at 01:41
  • @NickJohnson It proves that statement for single-scan algorithms. It doesn't assume it. A single-scan algorithm must be able to determine the remaining elements, and therefore must have at least N choose N/2 states at the half way point, and that requires at least linear space. – Craig Gidney Jul 26 '11 at 14:42
  • @Strilanc You're still assuming your conclusion. Why must a single-scan algorithm "be able to determine the remaining elements"? This clearly isn't the case in the summing example I gave, and you haven't demonstrated why it is the case for determining a permutation. – Nick Johnson Jul 27 '11 at 00:59
  • @NickJohnson No, I'm not assuming it. I've already stated the proof several times. In order for the algorithm to recognize a permutation in a single scan, it has to know either directly or indirectly which items remain. There are too many possibilities at the half-way point to fit into sub-linear space. The sum example is different because a sum takes less space than a permutation (logarithmic, assuming poly-bounded values, instead of linear). – Craig Gidney Jul 27 '11 at 02:07
  • @Strilanc But you still haven't shown that that's the only possible approach, you're just assuming it is. Take another example: determining if all the elements in an array are repeated exactly twice. Intuitively, this also requires O(n) space, but the xor solution requires only constant space. You can't simply assume that no such solution exists for this problem. – Nick Johnson Jul 27 '11 at 04:17
  • @NickJohnson You can't determine 'all elements appear exactly twice' with the xor method (1 xor 2 xor 3 == 0). A correct example is "which number in 1 to n has been omitted", which does have a constant-space solution. However, notice that the proof doesn't work for that example. An unbounded turing machine examining the remainder+state at the half-way point only needs the state to distinguish between n/2 possibilities instead of n choose n/2. That only proves a `logarithmic` space bound (note: the log comes from the high values requiring more bits, you can call it constant-size in practice). – Craig Gidney Jul 27 '11 at 15:59
  • @Strilanc The (interview) question assumes that at most one element will be singular, while all other elements will be repeated - in which case the xor solution works just fine, and even allows you to determine _which_ element wasn't repeated. My point stands: There are cases in which there's no obvious solution in less than linear space, but for which solutions nevertheless exist. You haven't proven that's not the case here, you've just asserted it's so. – Nick Johnson Jul 28 '11 at 00:49
  • @NickJohnson I showed your example was flawed. I don't think we're going to agree. – Craig Gidney Jul 28 '11 at 11:14
5

I doubt you would be able to prove that ;)

  (1, 2, 4, 4, 4, 5, 7, 9, 9)

I think that more generally, this problem isn't solvable by processing the numbers in order. Suppose you are processing the elements in order and you are halfway the array. Now the state of your program has to somehow reflect which numbers you've encountered so far. This requires at least O(n) bits to store.

Jules
  • 6,318
  • 2
  • 29
  • 40
  • Thanks! Rules that solution out. – INS May 20 '10 at 11:11
  • 2
    This is more for a comment than an answer, as it doesn't actually answer the question. – Joren May 20 '10 at 11:29
  • 1
    I agree, but it does rule out half of the "answers" further down as well as the approach that the OP was taking. So I believe it does solve part of the problem: you don't have to keep looking for a way to solve it by processing the elements in order. – Jules May 20 '10 at 15:48
3

This isn't going to work due to the complexity being given as a function of N rather than M, implying that N >> M

This was my shot at it, but for a bloom filter to be useful, you need a big M, at which point you may as well use simple bit toggling for something like integers

http://en.wikipedia.org/wiki/Bloom_filter

For each element in the array Run the k hash functions Check for inclusion in the bloom filter If it is there, there is a probability you've seen the element before If it isn't, add it

When you are done, you may as well compare it to the results of a 1..N array in order, as that'll only cost you another N.

Now if I haven't put enough caveats in. It isn't 100%, or even close since you specified complexity in N, which implies that N >> M, so fundamentally it won't work as you have specified it.

BTW, the false positive rate for an individual item should be e = 2^(-m/(n*sqrt(2)))

Which monkeying around with will give you an idea how big M would need to be to be acceptable.

McBeth
  • 1,177
  • 8
  • 12
  • Wouldn't that be O(n^2)? You say 'For each element ... compare it to the results ... that'll only cost you another N'. So N elements and then an additional N cost per element, N^2? – samoz May 20 '10 at 13:05
  • You skipped the "When you are done" bit. The final check is totally optional and would occur after the loop – McBeth May 21 '10 at 00:41
1

I don't know how to do it in O(N), or even if it can be done in O(N). I know that it can be done in O(N log N) if you (use an appropriate) sort and compare.

That being said, there are many O(N) techniques that can be done to show that one is NOT a permutation of the other.

  1. Check the length. If unequal, obviously not a permutation.
  2. Create an XOR fingerprint. If the value of all the elements XOR'ed together does not match, then it can not be a permutation. A match would however be inconclusive.
  3. Find the sum of all elements. Although the result may overflow, that should not be a worry when matching this 'fingerprint'. If however, you did a checksum that involved multiplying then overflow would be an issue.

Hope this helps.

Sparky
  • 13,505
  • 4
  • 26
  • 27
1

You might be able to do this in randomized O(n) time and constant space by computing sum(x_i) and product(x_i) modulo a bunch of different randomly chosen constants C of size O(n). This basically gets you around the problem that product(x_i) gets too large.

There's still a lot of open questions, though, like if sum(x_i)=N(N+1)/2 and product(x_i)=N! are sufficient conditions to guarantee a permutation, and what is the chance that a non-permutation generates a false positive (I would hope ~1/C for each C you try, but maybe not).

Keith Randall
  • 22,985
  • 2
  • 35
  • 54
0

it's a permutation if and only if there are no duplicate values in the array, should be easy to check that in O(N)

Chris Card
  • 3,216
  • 20
  • 15
0

Depending on how much space you have, relative to N, you might try using hashing and buckets.

That is, iterate over the entire list, hash each element, and store it in a bucket. You'll need to find a way to reduce bucket collisions from the hashes, but that is a solved problem.

If an element tries to go into a bucket with an item identical to it, it is a permutation.

This type of solution would be O(N) as you touch each element only once.

However, the problem with this is whether space M is larger than N or not. If M > N, this solution will be fine, but if M < N, then you will not be able to solve the problem with 100% accuracy.

samoz
  • 56,849
  • 55
  • 141
  • 195
  • Given that the question is O(N) time complexity with O(1) space complexity, there is by definition a big enough N where M < N. – Ants Aasma May 20 '10 at 11:48
  • @Ants Agreed, but maybe that O(1) space is on the order of gigabytes and N is much smaller. If this is known, he could use my solution. But agreed, this does require knowing a lot of information at the start of things. – samoz May 20 '10 at 13:03
  • 1
    The whole definition of the big-O concept is that N is large enough that the complexity class dominates everything else. Big O is always a theoretical exercise, practical considerations like how many gigabytes is available matters when solving real instances of a problem. – Ants Aasma May 20 '10 at 21:58
0

First, an information theoretic reason why this may be possible. We can trivially check that the numbers in the array are in bounds in O(N) time and O(1) space. To specify any such array of in-bounds numbers requires N log N bits of information. But to specify a permutation requires approximately (N log N) - N bits of information (Stirling's approximation). Thus, if we could acquire N bits of information during testing, we might be able to know the answer. This is trivial to do in N time (in fact, with M static space we can pretty easily acquire log M information per step, and under special circumstances we can acquire log N information).

On the other hand, we only get to store something like M log N bits of information in our static storage space, which is presumably much less than N, so it depends greatly what the shape of the decision surface is between "permutation" and "not".

I think that this is almost possible but not quite given the problem setup. I think one is "supposed" to use the cycling trick (as in the link that Iulian mentioned), but the key assumption of having a tail in hand fails here because you can index the last element of the array with a permutation.

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
0

The sum and the product will not guarantee the correct answer, since these hashes are subject to collisions, i.e. different inputs might potentially produce identical results. If you want a perfect hash, a single-number result that actually fully describes the numerical composition of the array, it might be the following.

Imagine that for any number i in [1, N] range you can produce a unique prime number P(i) (for example, P(i) is the i-th prime number). Now all you need to do is calculate the product of all P(i) for all numbers in your array. The product will fully and unambiguously describe the composition of your array, disregarding the ordering of values in it. All you need to do is to precalculate the "perfect" value (for a permutation) and compare it with the result for a given input :)

Of course, the algorithm like this does not immediately satisfy the posted requirements. But at the same time it is intuitively too generic: it allows you to detect a permutation of absolutely any numerical combination in an array. In your case you need to detect a permutation of a specific combination 1, 2, ..., N. Maybe this can somehow be used to simplify things... Probably not.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
0

Java solution below answers question partly. Time complexity I believe is O(n). (This belief based on the fact that solution doesn't contains nested loops.) About memory -- not sure. Question appears first on relevant requests in google, so it probably can be useful for somebody.

public static boolean isPermutation(int[] array) {   
    boolean result = true;
    array = removeDuplicates(array);
    int startValue = 1;
    for (int i = 0; i < array.length; i++) {
        if (startValue + i  != array[i]){
            return false;
        }
    }
    return result;
}
public static int[] removeDuplicates(int[] input){
    Arrays.sort(input);
    List<Integer> result = new ArrayList<Integer>();
    int current = input[0];
    boolean found = false;

    for (int i = 0; i < input.length; i++) {
        if (current == input[i] && !found) {
            found = true;
        } else if (current != input[i]) {
            result.add(current);
            current = input[i];
            found = false;
        }
    }
    result.add(current);
    int[] array = new int[result.size()];
    for (int i = 0; i < array.length ; i ++){
        array[i] = result.get(i);
    }
    return array;
}
public static void main (String ... args){
    int[] input = new int[] { 4,2,3,4,1};
    System.out.println(isPermutation(input));
    //output true
    input = new int[] { 4,2,4,1};
    System.out.println(isPermutation(input));
    //output false
}
Yuriy N.
  • 4,936
  • 2
  • 38
  • 31
0
int solution(int A[], int N) {
  int i,j,count=0, d=0, temp=0,max;
  for(i=0;i<N-1;i++) {
    for(j=0;j<N-i-1;j++) {
      if(A[j]>A[j+1]) {
        temp = A[j+1];
        A[j+1] = A[j];
        A[j] = temp;
      }
    }
  }
  max = A[N-1];
  for(i=N-1;i>=0;i--) {
    if(A[i]==max) {
      count++;
    }
    else {
      d++;
    }
    max = max-1;
  }
  if(d!=0) {
    return 0;
  }
  else {
    return 1;
  }
}
YoungHobbit
  • 13,254
  • 9
  • 50
  • 73
shubh_
  • 1
0

Alright, this is different, but it appears to work!

I ran this test program (C#):

    static void Main(string[] args) {
        for (int j = 3; j < 100; j++) {
            int x = 0;
            for (int i = 1; i <= j; i++) {
                x ^= i;
            }
            Console.WriteLine("j: " + j + "\tx: " + x + "\tj%4: " + (j % 4));
        }
    }

Short explanation: x is the result of all the XORs for a single list, i is the element in a particular list, and j is the size of the list. Since all I'm doing is XOR, the order of the elements don't matter. But I'm looking at what correct permutations look like when this is applied.

If you look at j%4, you can do a switch on that value and get something like this:

    bool IsPermutation = false;
    switch (j % 4) {
        case 0:
            IsPermutation = (x == j);
            break;
        case 1:
            IsPermutation = (x == 1);
            break;
        case 2:
            IsPermutation = (x == j + 1);
            break;
        case 3:
            IsPermutation = (x == 0);
            break;
    }

Now I acknowledge that this probably requires some fine tuning. It's not 100%, but it's a good easy way to get started. Maybe with some small checks running throughout the XOR loop, this could be perfected. Try starting somewhere around there.

Corey Ogburn
  • 24,072
  • 31
  • 113
  • 188
0

it looks like asking to find duplicate in array with stack machine.

it sounds impossible to know the full history of the stack , while you extract each number and have limited knowledge of the numbers that were taken out.

none
  • 4,669
  • 14
  • 62
  • 102
0

Here's proof it can't be done:

Suppose by some artifice you have detected no duplicates in all but the last cell. Then the problem reduces to checking if that last cell contains a duplicate.

If you have no structured representation of the problem state so far, then you are reduced to performing a linear search over the entire previous input, for EACH cell. It's easy to see how this leaves you with a quadratic-time algorithm.

Now, suppose through some clever data structure that you actually know which number you expect to see last. Then certainly that knowledge takes at least enough bits to store the number you seek -- perhaps one memory cell? But there is a second-to-last number and a second-to-last sub-problem: then you must similarly represent a set of two possible numbers yet-to-be-seen. This certainly requires more storage than encoding only for one remaining number. By a progression of similar arguments, the size of the state must grow with the size of the problem, unless you're willing to accept a quadratic-time worst-case.

This is the time-space trade-off. You can have quadratic time and constant space, or linear time and linear space. You cannot have linear time and constant space.

Ian
  • 4,421
  • 1
  • 20
  • 17
0

Check out the following solution. It uses O(1) additional space. It alters the array during the checking process, but returns it back to its initial state at the end.

The idea is:

  1. Check if any of the elements is out of the range [1, n] => O(n).
  2. Go over the numbers in order (all of them are now assured to be in the range [1, n]), and for each number x (e.g. 3):

    • go to the x'th cell (e.g. a[3]), if it's negative, then someone already visited it before you => Not permutation. Otherwise (a[3] is positive), multiply it by -1. => O(n).
  3. Go over the array and negate all negative numbers.

This way, we know for sure that all elements are in the range [1, n], and that there are no duplicates => The array is a permutation.

int is_permutation_linear(int a[], int n) {
    int i, is_permutation = 1;

    // Step 1.
    for (i = 0; i < n; ++i) {
        if (a[i] < 1 || a[i] > n) {
            return 0;
        }
    }

    // Step 2.
    for (i = 0; i < n; ++i) {
        if (a[abs(a[i]) - 1] < 0) {
            is_permutation = 0;
            break;
        }
        a[i] *= -1;
    }

    // Step 3.
    for (i = 0; i < n; ++i) {
        if (a[i] < 0) {
            a[i] *= -1;
        }
    }

    return is_permutation;
}

Here is the complete program that tests it:

/*
 * is_permutation_linear.c
 *
 *  Created on: Dec 27, 2011
 *      Author: Anis
 */

#include <stdio.h>

int abs(int x) {
    return x >= 0 ? x : -x;
}

int is_permutation_linear(int a[], int n) {
    int i, is_permutation = 1;

    for (i = 0; i < n; ++i) {
        if (a[i] < 1 || a[i] > n) {
            return 0;
        }
    }

    for (i = 0; i < n; ++i) {
        if (a[abs(a[i]) - 1] < 0) {
            is_permutation = 0;
            break;
        }
        a[abs(a[i]) - 1] *= -1;
    }

    for (i = 0; i < n; ++i) {
        if (a[i] < 0) {
            a[i] *= -1;
        }
    }

    return is_permutation;
}

void print_array(int a[], int n) {
    int i;
    for (i = 0; i < n; i++) {
        printf("%2d ", a[i]);
    }
}

int main() {
    int arrays[9][8] = { { 1, 2, 3, 4, 5, 6, 7, 8 },
                         { 8, 6, 7, 2, 5, 4, 1, 3 },
                         { 0, 1, 2, 3, 4, 5, 6, 7 },
                         { 1, 1, 2, 3, 4, 5, 6, 7 },
                         { 8, 7, 6, 5, 4, 3, 2, 1 },
                         { 3, 5, 1, 6, 8, 4, 7, 2 },
                         { 8, 3, 2, 1, 4, 5, 6, 7 },
                         { 1, 1, 1, 1, 1, 1, 1, 1 },
                         { 1, 8, 4, 2, 1, 3, 5, 6 } };
    int i;

    for (i = 0; i < 9; i++) {
        printf("array: ");
        print_array(arrays[i], 8);
        printf("is %spermutation.\n",
               is_permutation_linear(arrays[i], 8) ? "" : "not ");
        printf("after: ");
        print_array(arrays[i], 8);
        printf("\n\n");

    }

    return 0;
}

And its output:

array:  1  2  3  4  5  6  7  8 is permutation.
after:  1  2  3  4  5  6  7  8 

array:  8  6  7  2  5  4  1  3 is permutation.
after:  8  6  7  2  5  4  1  3 

array:  0  1  2  3  4  5  6  7 is not permutation.
after:  0  1  2  3  4  5  6  7 

array:  1  1  2  3  4  5  6  7 is not permutation.
after:  1  1  2  3  4  5  6  7 

array:  8  7  6  5  4  3  2  1 is permutation.
after:  8  7  6  5  4  3  2  1 

array:  3  5  1  6  8  4  7  2 is permutation.
after:  3  5  1  6  8  4  7  2 

array:  8  3  2  1  4  5  6  7 is permutation.
after:  8  3  2  1  4  5  6  7 

array:  1  1  1  1  1  1  1  1 is not permutation.
after:  1  1  1  1  1  1  1  1 

array:  1  8  4  2  1  3  5  6 is not permutation.
after:  1  8  4  2  1  3  5  6 
Anis Abboud
  • 1,328
  • 2
  • 16
  • 23