How to tell if an array is a permutation in O(n)?

后端 未结 16 1595
粉色の甜心
粉色の甜心 2020-12-07 10:17

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<

相关标签:
16条回答
  • 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;
      }
    }
    
    0 讨论(0)
  • 2020-12-07 10:22

    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
    }
    
    0 讨论(0)
  • 2020-12-07 10:23

    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 
    
    0 讨论(0)
  • 2020-12-07 10:25

    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.

    0 讨论(0)
  • 2020-12-07 10:25

    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).

    0 讨论(0)
  • 2020-12-07 10:28

    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.

    0 讨论(0)
提交回复
热议问题