在O(n)时间和O(1)空间中查找重复项

时间:2011-04-21 03:02:57

标签: c++ c algorithm

输入:给定n个元素的数组,其中包含从0到n-1的元素,这些数字中的任何一个都会出现任意次数。

目标:在O(n)中找到这些重复数字并仅使用常量存储空间。

例如,设n为7,数组为{1,2,3,1,3,0,6},答案应为1& 3。 我在这里检查了类似的问题,但答案使用了一些数据结构,如HashSet等。

任何有效的算法?

15 个答案:

答案 0 :(得分:162)

这是我提出的,不需要额外的符号位:

for i := 0 to n - 1
    while A[A[i]] != A[i] 
        swap(A[i], A[A[i]])
    end while
end for

for i := 0 to n - 1
    if A[i] != i then 
        print A[i]
    end if
end for

第一个循环置换数组,以便如果元素x至少出现一次,那么其中一个条目将位于A[x]位置。

请注意,它可能不会在第一次看起来像O(n),但它是 - 虽然它有一个嵌套循环,它仍然在O(N)时间运行。只有i这样A[i] != i时才会发生交换,并且每个交换都设置至少一个元素A[i] == i,之前不是真的while。这意味着交换总数(以及N-1循环体的总执行次数)最多为x

第二个循环打印A[x]不等于x的{​​{1}}的值 - 因为第一个循环保证如果x至少存在一次A[x]数组,其中一个实例将位于x,这意味着它会打印数组中不存在的{{1}}值。

(Ideone link so you can play with it)

答案 1 :(得分:33)

caf's brilliant answer打印出数组k-1次出现k次的每个数字。这是有用的行为,但问题可能要求每个副本只打印一次,并且他暗示可以在不吹动线性时间/常数空间界限的情况下这样做。这可以通过用以下伪代码替换他的第二个循环来完成:

for (i = 0; i < N; ++i) {
    if (A[i] != i && A[A[i]] == A[i]) {
        print A[i];
        A[A[i]] = i;
    }
}

这利用了在第一个循环运行后,如果任何值m出现多次,则保证其中一个外观位于正确位置的属性,即A[m]。如果我们小心,我们可以使用该“家”位置来存储有关是否已打印任何重复的信息。

在caf的版本中,当我们浏览数组时,A[i] != i暗示A[i]是重复的。在我的版本中,我依赖于稍微不同的不变量:A[i] != i && A[A[i]] == A[i]暗示A[i]是我们之前未见过的重复的。 (如果你放弃了“我们之前没见过的”部分,可以看出其余部分是由于caf的不变量的真实性所暗示的,并保证所有副本在家中都有一些副本。)此属性保留在一开始(在caf的第一个循环完成之后),我在下面显示它在每个步骤后都保持。

在我们浏览数组时,测试A[i] != i部分的成功意味着A[i] 可能是以前从未见过的副本。如果我们之前没有看到它,那么我们期望A[i]的归属位置指向它自己 - 这就是if条件的后半部分所测试的内容。如果是这种情况,我们打印它并改变原始位置以指回第一个找到的副本,创建一个两步“循环”。

要查看此操作不会改变我们的不变量,假设m = A[i]对于满足i的特定位置A[i] != i && A[A[i]] == A[i]。很明显,我们所做的更改(A[A[i]] = i)将通过导致其m条件的后半部分失败来防止其他非本地出现的if作为重复项输出,但当i到达家乡m时,它会有效吗?是的,它会,因为现在,即使在这个新的i我们发现if条件的前半部分A[i] != i是真的,下半部分测试它是否指向它的位置是一个家庭的位置,发现它不是。在这种情况下,我们不再知道mA[m]是否为重复值,但我们知道无论哪种方式,已经报告了,因为这些2个周期是保证不会出现在caf的第一个循环的结果中。 (请注意,如果m != A[m],则mA[m]中只有一个出现多次,而另一个则根本不出现。)

答案 2 :(得分:22)

这是伪代码

for i <- 0 to n-1:
   if (A[abs(A[i])]) >= 0 :
       (A[abs(A[i])]) = -(A[abs(A[i])])
   else
      print i
end for

Sample code in C++

答案 3 :(得分:2)

对于相对较小的N,我们可以使用div / mod操作

n.times do |i|
  e = a[i]%n
  a[e] += n
end

n.times do |i| 
  count = a[i]/n
  puts i if count > 1
end

不是C / C ++,但无论如何

http://ideone.com/GRZPI

答案 4 :(得分:1)

不是很漂亮,但至少很容易看到O(N)和O(1)属性。基本上我们扫描数组,对于每个数字,我们看到相应的位置是否已被标记为已经看过一次(N)或已经看过多次(N + 1)。如果它被标记为已经看过一次,我们打印它并标记已经看过多次。如果它没有被标记,我们标记它已经看过一次,我们将相应索引的原始值移动到当前位置(标记是破坏性操作)。

for (i=0; i<a.length; i++) {
  value = a[i];
  if (value >= N)
    continue;
  if (a[value] == N)  {
    a[value] = N+1; 
    print value;
  } else if (a[value] < N) {
    if (value > i)
      a[i--] = a[value];
    a[value] = N;
  }
}

或者,更好(尽管有双循环,速度更快):

for (i=0; i<a.length; i++) {
  value = a[i];
  while (value < N) {
    if (a[value] == N)  {
      a[value] = N+1; 
      print value;
      value = N;
    } else if (a[value] < N) {
      newvalue = value > i ? a[value] : N;
      a[value] = N;
      value = newvalue;
    }
  }
}

答案 5 :(得分:1)

C中的一个解决方案是:

#include <stdio.h>

int finddup(int *arr,int len)
{
    int i;
    printf("Duplicate Elements ::");
    for(i = 0; i < len; i++)
    {
        if(arr[abs(arr[i])] > 0)
          arr[abs(arr[i])] = -arr[abs(arr[i])];
        else if(arr[abs(arr[i])] == 0)
        {
             arr[abs(arr[i])] = - len ;
        }
        else
          printf("%d ", abs(arr[i]));
    }

}
int main()
{   
    int arr1[]={0,1,1,2,2,0,2,0,0,5};
    finddup(arr1,sizeof(arr1)/sizeof(arr1[0]));
    return 0;
}

是O(n)时间和O(1)空间复杂度。

答案 6 :(得分:1)

让我们假设我们将这个数组表示为单向图数据结构 - 每个数字都是一个顶点,它在数组中的索引指向另一个顶点,形成图的边缘。

为了更简单,我们有索引0到n-1和数字范围从0..n-1。  例如

   0  1  2  3  4 
 a[3, 2, 4, 3, 1]

0(3) - &gt; 3(3)是一个循环。

答案:只需依靠索引遍历数组。如果a [x] = a [y]则它是一个循环,因此重复。跳到下一个索引并再次继续,依此类推,直到数组结束。 复杂性:O(n)时间和O(1)空间。

答案 7 :(得分:0)

一个微小的python代码,用于演示上面的caf方法:

a = [3, 1, 1, 0, 4, 4, 6] 
n = len(a)
for i in range(0,n):
    if a[ a[i] ] != a[i]: a[a[i]], a[i] = a[i], a[a[i]]
for i in range(0,n):
    if a[i] != i: print( a[i] )

答案 8 :(得分:0)

在以下C函数中可以很容易地看到算法。检索原始数组虽然不是必需的,但可以将每个条目模数为 n

void print_repeats(unsigned a[], unsigned n)
{
    unsigned i, _2n = 2*n;
    for(i = 0; i < n; ++i) if(a[a[i] % n] < _2n) a[a[i] % n] += n;
    for(i = 0; i < n; ++i) if(a[i] >= _2n) printf("%u ", i);
    putchar('\n');
}

Ideone Link for testing.

答案 9 :(得分:0)

static void findrepeat()
{
    int[] arr = new int[7] {0,2,1,0,0,4,4};

    for (int i = 0; i < arr.Length; i++)
    {
        if (i != arr[i])
        {
            if (arr[i] == arr[arr[i]])
            {
                Console.WriteLine(arr[i] + "!!!");
            }

            int t = arr[i];
            arr[i] = arr[arr[i]];
            arr[t] = t;
        }
    }

    for (int j = 0; j < arr.Length; j++)
    {
        Console.Write(arr[j] + " ");
    }
    Console.WriteLine();

    for (int j = 0; j < arr.Length; j++)
    {
        if (j == arr[j])
        {
            arr[j] = 1;
        }
        else
        {
            arr[arr[j]]++;
            arr[j] = 0;
        }
    }

    for (int j = 0; j < arr.Length; j++)
    {
        Console.Write(arr[j] + " ");
    }
    Console.WriteLine();
}

答案 10 :(得分:0)

我迅速创建了一个示例游乐场应用程序,用于查找时间复杂度为0(n)和恒定额外空间的重复项。 请检查网址Finding Duplicates

IMP 以上解决方案适用于数组包含0到n-1的元素,且其中任何数字出现多次的情况。

答案 11 :(得分:0)

在此处查看说明https://youtu.be/qJ_Y7pKP0e4

此处代码https://github.com/TechieExpress/DataStructures/blob/main/findDuplicates

代码片段:

contract SafeMoon

答案 12 :(得分:0)

我们可以通过 -

来完成 O(n) 时间和 O(1) 空间复杂度
  1. 取第 i 个数组元素。

  2. 如果是负数,则为 +ve

  3. 最后,从数组索引(第 i 个元素)得到的数字乘以 -1。

  4. 如果数字为正,则返回索引。

     def findDuplicate(self, arr: List[int]) -> int:
         n=len(arr)
         for i in range(0,n):
    
             arr[(abs(arr[i]))-1]=arr[(abs(arr[i]))-1]*(-1)
             if arr[(abs(arr[i]))-1]>0:
                 return abs(arr[i])
    

答案 13 :(得分:-1)

Dim ws As Worksheet 
Set ws = ActiveSheet
With ws
    .Columns("A:A").ColumnWidth = 17.86
    .Columns("B:C").ColumnWidth = 19.86
    .Columns("D:I").ColumnWidth = 10.86
End With

答案 14 :(得分:-2)

如果阵列不是太大,这个解决方案更简单, 它创建了另一个相同大小的数组用于滴答。

1创建与输入数组相同大小的位图/数组

 int check_list[SIZE_OF_INPUT];
 for(n elements in checklist)
     check_list[i]=0;    //initialize to zero

2扫描输入数组并在上面的数组中增加其计数

for(i=0;i<n;i++) // every element in input array
{
  check_list[a[i]]++; //increment its count  
}  

3现在扫描check_list数组并打印副本一次或多次复制

for(i=0;i<n;i++)
{

    if(check_list[i]>1) // appeared as duplicate
    {
        printf(" ",i);  
    }
}

当然,上面给出的解决方案占用的空间是两倍,但时间效率是O(2n),基本上是O(n)。