如何判断数组是否是O(n)中的排列?

时间:2010-05-20 10:44:42

标签: arrays algorithm permutation

输入:N个元素的只读数组,包含从1到N的整数值(某些整数值可以出现多次!)。并且固定大小的记忆区域(10,100,1000等 - ,取决于N)。

如果数组代表一个排列,如何告诉在O(n)

- 到目前为止我取得了什么(答案证明这是好): -

<击>

<击>
  1. 我使用有限的内存区域来存储数组的总和和产品。
  2. 我将总和与 N *(N + 1)/ 2 进行比较,并将产品与 N!进行比较
  3. 我知道如果条件(2)为真,我可能有一个排列。我想知道是否有办法证明条件(2)足以判断我是否有排列。到目前为止,我还没想出来......

    <击>

16 个答案:

答案 0 :(得分:16)

我有点怀疑有一个解决方案。你的问题似乎与几年前在数学文献中提出的问题非常接近,a summary given here ("The Duplicate Detection Problem", S. Kamal Abdali, 2003)使用循环检测 - 这个想法如下:

如果存在重复,则在1和N之间存在数字j,以便以下将导致无限循环:

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

因为排列由不同元素s 0 ,s 1 ,... s k-1 的一个或多个子集S组成其中s j = a [s j-1 ]表示1和k-1之间的所有j,s 0 = a [s < sub> k-1 ],因此所有元素都参与循环 - 其中一个副本不会成为这样一个子集的一部分。

e.g。如果数组= [2,1,4,6, 8 ,7,9,3,8]

然后在位置5处以粗体显示的元素是重复的,因为所有其他元素形成循环:{2 - &gt; 1,4-&gt; 6 - &gt; 7 - &gt; 9 - &gt; 8 - &gt; 3}。阵列[2,1,4,6,5,7,9,3,8]和[2,1,4,6,3,7,9,5,8]是有效排列(周期为{2} - &gt; 1,4-> 6-> 7-> 9-> 8-> 3,5}和{2-> 1,4-> 6-> 7-> 9 - 分别是&gt; 8 - &gt; 5 - &gt; 3}。

Abdali开始寻找重复的方法。基本上,以下算法(使用Floyd's cycle-finding algorithm)适用于您遇到的其中一个重复项:

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);
}

困难在于我不确定你所说的问题是否与他论文中的问题相符,而且我也不确定他描述的方法是在O(N)中运行还是使用固定数量的空间。一个潜在的反例是以下数组:

[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]

基本上是身份置换移位2,元素[N-6,N-4和N-2]被[N-2,N-5,N-5]取代。这有正确的总和(不是正确的产品,但我拒绝将产品作为一种可能的检测方法,因为用任意精度算术计算N!的空间要求是O(N),这违反了“固定存储空间”的精神要求),如果你试图寻找周期,你将获得周期{3 - > 5 - &gt; 7 - &gt; 9 - &gt; ...... N-7 - &gt; N-5 - &gt; N-1}和{4 - > 6 - &gt; 8 - &gt; ...... N-10 - &gt; N-8 - &gt; N-2 - > N - >; 2}。问题是可能有多达N个周期(身份置换有N个周期),每个周期都占用O(N)来找到重复,你必须跟踪某些已经跟踪的周期和哪些没有。我怀疑在固定的空间内可以做到这一点。但也许是。

这是一个非常严重的问题,值得在mathoverflow.net上询问(尽管大多数时候mathoverflow.net都是在stackoverflow上引用的,但是这些问题太容易了)


编辑:我做了ask on mathoverflow,那里有一些有趣的讨论。

答案 1 :(得分:10)

这在O(1)空间中是不可能的,至少使用单扫描算法。

<强>证明

假设您已经处理了N个元素中的N / 2个。假设序列是一个置换,那么,给定算法的状态,你应该能够找出N / 2个剩余元素的集合。如果你无法找出剩余的元素,那么可以通过重复一些旧元素来欺骗算法。

有N个选择N / 2个可能的剩余集。它们中的每一个都必须由算法的不同内部状态表示,否则你无法找出剩余的元素。但是,它需要对数空间来存储X状态,因此需要BigTheta(log(N选择N / 2))空间来存储N选择N / 2个状态。该值随N增长,因此算法的内部状态不能适合O(1)空间。

更多正式证明

你想要创建一个程序P,给定最后的N / 2个元素和线性时间常数空间算法在处理N / 2个元素后的内部状态,确定整个序列是否是一个排列1..N。此次要计划没有时间或空间限制。

假设P存在,我们可以创建一个程序Q,只采用线性时间常数空间算法的内部状态,该算法确定序列的必要最终N / 2个元素(如果它是一个置换)。 Q通过传递P每个可能的最终N / 2个元素并返回P返回true的集合来工作。

但是,因为Q有N选择N / 2个可能的输出,所以它必须至少有N个选择N / 2个可能的输入。这意味着原始算法的内部状态必须存储至少N个选择N / 2个状态,需要BigTheta(log N选择N / 2),这大于常数大小。

因此,具有时间和空间界限的原始算法如果具有恒定大小的内部状态,也无法正常工作。

[我认为这个想法可以概括,但思考并不能证明。]

<强>后果

BigTheta(log(N选择N / 2))等于BigTheta(N)。因此,只要使用布尔数组并在遇到它们时勾选值(可能)是空间最优的,并且时间也是最佳的,因为它需要线性时间。

答案 2 :(得分:5)

我怀疑你能否证明这一点;)

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

我认为更一般地说,通过按顺序处理数字无法解决这个问题。假设您按顺序处理元素,并且您在数组的中间。现在你的程序状态必须以某种方式反映你到目前为止遇到的数字。这需要至少存储O(n)位。

答案 3 :(得分:3)

由于作为N而不是M的函数给出的复杂性,这不起作用,暗示N>&gt;中号

这是我的镜头,但是对于一个有用的布隆过滤器,你需要一个大的M,此时你也可以使用简单的位切换为整数这样的东西

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

对于数组中的每个元素   运行k哈希函数   检查包含在bloom过滤器中   如果它在那里,你有可能以前见过这个元素   如果不是,请添加

当你完成后,你也可以按顺序将它与1..N数组的结果进行比较,因为这只会花费你另一个N.

现在,如果我没有提出足够的警告。它不是100%,甚至是关闭,因为你指定了N的复杂性,这意味着N&gt;&gt; M,所以从根本上说它不会像你指定的那样起作用。

顺便说一下,个别项目的误报率应该是 e = 2 ^( - m /(n * sqrt(2)))

周围的人会让你知道M需要多大才能被接受。

答案 4 :(得分:1)

我不知道如何在O(N)中完成它,或者即使它可以在O(N)中完成。我知道如果你(使用适当的)排序和比较它可以在O(N log N)中完成。

话虽如此,有许多O(N)技术可以表明一个不是另一个的排列。

  1. 检查长度。如果不平等,显然不是排列。
  2. 创建XOR指纹。如果XOR一起的所有元素的值不匹配,则它不能是排列。然而,比赛将是不确定的。
  3. 找出所有元素的总和。虽然结果可能会溢出,但在匹配此“指纹”时不应该担心。但是,如果你做了一个涉及乘法的校验和,那么溢出将成为一个问题。
  4. 希望这有帮助。

答案 5 :(得分:1)

您可以通过计算O(n)sum(x_i)模数一堆随机选择的大小为product(x_i)的常量C,在随机O(n)时间和常量空间中执行此操作。这基本上可以解决product(x_i)过大的问题。

但仍然存在许多未解决的问题,例如,如果sum(x_i)=N(N+1)/2product(x_i)=N!是保证排列的充分条件,那么非排列会产生误报的可能性是多少(I希望你尝试的每个C都能达到1 / C,但也许不会。)

答案 6 :(得分:0)

这是一种排列,当且仅当数组中没有重复值时,应该很容易在O(N)中检查

答案 7 :(得分:0)

根据您有多少空间,相对于N,您可以尝试使用散列和存储桶。

即,遍历整个列表,散列每个元素,并将其存储在存储桶中。您需要找到一种方法来减少哈希中的桶冲突,但这是一个已解决的问题。

如果一个元素试图进入一个包含与之相同的项目的存储桶,那么它就是一个排列。

当你只触摸每个元素一次时,这种类型的解决方案将是O(N)。

然而,问题在于空间M是否大于N.如果M> N,这个解决方案没问题,但如果M&lt; N,那么你将无法以100%的准确率解决问题。

答案 8 :(得分:0)

首先,这可能是信息理论的原因。我们可以简单地检查数组中的数字是否在O(N)时间和O(1)空间的边界内。要指定任何此类入站数字数组,需要N log N位信息。但是要指定排列需要大约(N log N) - N位信息(斯特林的近似值)。因此,如果我们可以在测试期间获取N位信息,我们可能能够知道答案。这在N时间内很简单(事实上,对于M静态空间,我们可以非常轻松地获取每个步骤的log M个信息,并且在特殊情况下我们可以获得log N信息)。

另一方面,我们只能在我们的静态存储空间中存储类似M log N位的信息,这可能远小于N,所以它大大取决于它的形状决策面在“排列”和“不”之间。

我认为这几乎可能但不完全给出问题设置。我认为一个人“应该”使用循环技巧(如Iulian提到的链接),但是有一个尾巴的关键假设在这里失败,因为你可以索引最后一个元素带排列的数组。

答案 9 :(得分:0)

总和和产品不能保证正确答案,因为这些哈希值会发生冲突,即不同的输入可能会产生相同的结果。如果你想要一个完美的哈希,一个实际完全描述数组数字组成的单数结果,它可能如下。

想象一下,对于i范围内的任何数字[1, N],您都可以生成唯一素数P(i)(例如,P(i)是第i个素数)。现在您需要做的就是计算数组中所有数字的所有P(i)的乘积。该产品将完全无误地描述数组的组成,无论其中的值的排序如何。您需要做的就是预先计算“完美”值(对于置换)并将其与给定输入的结果进行比较:)

当然,这样的算法并不能立即满足发布的要求。但与此同时,它直观地过于通用:它允许您检测数组中绝对任何数值组合的排列。在您的情况下,您需要检测特定组合1, 2, ..., N的排列。也许这可以用某种方式来简化事情......可能不会。

答案 10 :(得分:0)

好吧,这是不同的,但似乎有效!

我运行了这个测试程序(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));
        }
    }

简短说明:x是单个列表的所有XOR的结果,i是特定列表中的元素,j是列表的大小。由于我所做的只是异或,所以元素的顺序无关紧要。但我正在研究在应用这种情况时正确的排列是什么样的。

如果你看一下j%4,你可以对这个值进行切换,得到类似的结果:

    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;
    }

现在我承认这可能需要一些微调。这不是100%,但它是一个很容易的入门方式。也许在整个XOR循环中运行一些小检查,这可能是完美的。尝试从那里开始。

答案 11 :(得分:0)

它看起来像是要求在堆栈机器中找到重复的数组。

听起来不可能知道堆栈的完整历史记录,而你提取每个数字并且对所取出的数字知之甚少。

答案 12 :(得分:0)

这是证明无法完成:

假设有一些技巧,除了最后一个细胞外,你没有发现任何重复。然后问题就减少到检查最后一个单元格是否包含重复项。

如果到目前为止您的问题状态的结构化表示,那么对于每个单元格,您将简化为对整个先前输入执行线性搜索。通过二次时间算法很容易看出它是如何离开你的。

现在,假设通过一些聪明的数据结构,您实际上知道您希望最后看到哪个数字。那么知识肯定需要至少足够的位来存储你寻找的数字 - 也许是一个存储单元?但是有一个倒数第二个数字和倒数第二个子问题:那么你必须同样代表一组两个可能尚未被看到的数字。这肯定需要比仅对剩余数字编码更多的存储空间。通过类似论证的进展,国家的规模必须随问题的大小而增长,除非你愿意接受二次时最坏情况。

这是时空权衡。您可以拥有二次时间和常数空间,或线性时间和线性空间。你不能拥有线性时间和恒定空间。

答案 13 :(得分:0)

查看以下解决方案。它使用O(1)附加空间。 它在检查过程中改变数组,但最后将其返回到初始状态。

这个想法是:

  1. 检查是否有任何元素超出范围[1,n] =&gt;为O(n)。
  2. 按顺序查看数字(现在确保所有数字都在[1,n]范围内),并且对于每个数字x(例如3):

    • 转到第x个单元格(例如[3]),如果它是否定的,那么有人已经访问过它=>&gt;不是排列。否则(a [3]为正),将其乘以-1。 =&GT;为O(n)。
  3. 浏览阵列并否定所有负数。
  4. 这样,我们确信所有元素都在[1,n]范围内,并且没有重复项=&gt;数组是一个排列。

    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;
    }
    

    以下是测试它的完整程序:

    /*
     * 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;
    }
    

    及其输出:

    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 
    

答案 14 :(得分:0)

下面的Java解决方案部分回答了问题。我相信时间复杂度是O(n)。 (这种信念基于解决方案不包含嵌套循环的事实。)关于内存 - 不确定。问题首先出现在谷歌的相关请求中,因此它可能对某些人有用。

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
}

答案 15 :(得分: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;
  }
}