在数组中查找两个重复数字的算法,无需排序

时间:2009-02-17 06:56:00

标签: algorithm search

有一个大小为n的数组(数字介于0和n - 3之间),只重复2个数字。元素随机放置在数组中。

E.g。在{2,3,6,1,5,4,0,3,5}中n = 9,重复数为3和5.

查找重复数字的最佳方法是什么?

P.S。 [你不应该使用排序]

25 个答案:

答案 0 :(得分:27)

如果您知道可能的输入域是什么,那么 O(n)解决方案。例如,如果输入数组包含0到100之间的数字,请考虑以下代码。

bool flags[100];
for(int i = 0; i < 100; i++)
    flags[i] = false;

for(int i = 0; i < input_size; i++)
    if(flags[input_array[i]])
         return input_array[i];
    else       
        flags[input_array[i]] = true;

当然还有额外的内存,但这是最快的。

答案 1 :(得分:21)

好的,好像我只是不能休息一下:)

最简单的解决方案

int A[N] = {...};

int signed_1(n) { return n%2<1 ? +n : -n;  } // 0,-1,+2,-3,+4,-5,+6,-7,...
int signed_2(n) { return n%4<2 ? +n : -n;  } // 0,+1,-2,-3,+4,+5,-6,-7,...

long S1 = 0;  // or int64, or long long, or some user-defined class
long S2 = 0;  // so that it has enough bits to contain sum without overflow

for (int i=0; i<N-2; ++i)
{
   S1 += signed_1(A[i]) - signed_1(i);
   S2 += signed_2(A[i]) - signed_2(i);
} 

for (int i=N-2; i<N; ++i)
{
   S1 += signed_1(A[i]);
   S2 += signed_2(A[i]);
} 

S1 = abs(S1);
S2 = abs(S2);

assert(S1 != S2);  // this algorithm fails in this case

p = (S1+S2)/2;
q = abs(S1-S2)/2;

一个总和(S1或S2)包含具有相同符号的p和q,另一个总和 - 具有相反的符号,所有其他成员都被消除。
S1和S2必须有足够的位来容纳和,因为abs()算法不能代表溢出。

如果abs(S1)== abs(S2)那么算法失败,尽管这个值仍然是p和q之间的差值(即abs(p-q)== abs(S1))。

以前的解决方案

我怀疑有人会在现场遇到这样的问题;)
我想,我知道老师的期望:

让我们取数组{0,1,2,...,n-2,n-1},
给定的一个可以通过用未知的p和q(较少的顺序)替换最后两个元素n-2和n-1来生成

所以,元素的总和将是(n-1)n / 2 + p + q - (n-2) - (n-1) <登记/> 平方和(n-1)n(2n-1)/ 6 + p ^ 2 + q ^ 2 - (n-2)^ 2 - (n-1)^ 2

简单的数学仍然存在:

  (1)  p+q = S1  
  (2)  p^2+q^2 = S2

当然,你不会解决它,因为数学课教授解方形方程。

首先,计算模2 ^ 32的所有模数,即允许溢出 然后检查对{p,q}:{0,S1},{1,S1-1} ...对表达式(2)以找到候选人(由于模数和平方可能超过2)< / em>的
最后检查找到的候选人是否确实存在于阵列中两次。

答案 2 :(得分:12)

您知道您的数组包含0到n-3之间的每个数字以及两个重复数字(p&amp; q)。为简单起见,我们暂时忽略0-case。

您可以计算数组上的总和和产品,结果是:

1 + 2 + ... + n-3 + p + q = p + q + (n-3)(n-2)/2

所以如果你从整个数组的总和中减去(n-3)(n-2)/ 2,你就得到了

sum(Array) - (n-3)(n-2)/2 = x = p + q

现在对产品做同样的事情:

1 * 2 * ... * n - 3 * p * q = (n - 3)! * p * q

prod(Array) / (n - 3)! = y = p * q

你现在有了这些条款:

x = p + q

y = p * q

=> y(p + q) = x(p * q)

如果你改变这个术语,你应该能够计算p和q

答案 3 :(得分:7)

将每个元素插入到一个set / hashtable中,首先检查它是否已经存在于其中。

答案 4 :(得分:7)

您可以利用sum(array)=(n-2)*(n-3)/ 2 +两个缺失数字这一事实。

编辑:正如其他人已经注意到的,结合方格和,你可以使用它,我只是有点慢搞清楚。

答案 5 :(得分:6)

查看关于该主题的这篇陈旧但好的论文:

答案 6 :(得分:3)

问题的一些答案:Algorithm to determine if array contains n…n+m?包含您可以为您的目的采用的子问题解决方案。

例如,这是my answer的相关部分:

bool has_duplicates(int* a, int m, int n)
{
  /** O(m) in time, O(1) in space (for 'typeof(m) == typeof(*a) == int')

      Whether a[] array has duplicates.

      precondition: all values are in [n, n+m) range.

      feature: It marks visited items using a sign bit.
  */
  assert((INT_MIN - (INT_MIN - 1)) == 1); // check n == INT_MIN
  for (int *p = a; p != &a[m]; ++p) {
    *p -= (n - 1); // [n, n+m) -> [1, m+1)
    assert(*p > 0);
  }

  // determine: are there duplicates
  bool has_dups = false;
  for (int i = 0; i < m; ++i) {
    const int j = abs(a[i]) - 1;
    assert(j >= 0);
    assert(j < m);
    if (a[j] > 0)
      a[j] *= -1; // mark
    else { // already seen
      has_dups = true;
      break;
    }
  }

  // restore the array
  for (int *p = a; p != &a[m]; ++p) {
    if (*p < 0) 
      *p *= -1; // unmark
    // [1, m+1) -> [n, n+m)
    *p += (n - 1);        
  }

  return has_dups; 
}

程序保持数组不变(数组应该是可写的,但它的值在退出时恢复)。

适用于最大INT_MAX的数组大小(在64位系统上为9223372036854775807)。

答案 7 :(得分:2)

我知道这个问题已经很老了,但我突然发现了它,我想我有一个有趣的答案。 我们知道这是一个脑力激荡器和一个简单的解决方案(即HashMap,Sort等),无论它们有多好,都会很无聊。

由于数字是整数,它们具有恒定的比特大小(即32)。我们假设我们正在使用4位整数。我们会寻找 A B 这些重复的数字。

我们需要4个桶,每个桶一个。每个桶包含其特定位为1的数字。例如,存储桶1获得2,3,4,7,...:

Bucket 0 : Sum ( x where: x & 2 power 0 == 0 )
...
Bucket i : Sum ( x where: x & 2 power i == 0 )

如果没有重复,我们知道每个桶的总和是多少。我认为这是先验知识。

一旦生成了以上存储桶,其中一堆将具有超出预期的值。通过从桶中构建数字,我们将拥有(A OR B供您参考)。

我们可以按如下方式计算(A XOR B)

A XOR B = Array[i] XOR Array[i-1] XOR ... 0, XOR n-3 XOR n-2  ... XOR 0

现在回到桶,我们确切地知道哪些桶都有我们的数字,哪些桶只有一个(来自XOR位)。

对于只有一个数字的桶,我们可以提取数字num =(总和 - 预期的桶总和)。但是,只有当我们找到一个重复的数字时我们应该是好的,所以如果我们在A XOR B中至少有一个位,我们就得到了答案。

但是,如果A XOR B为零怎么办? 那么这种情况只有在两个重复的数字都是相同的数字时才有可能,那么我们的数字就是A或B的答案。

答案 8 :(得分:2)

suppose array is

a[0], a[1], a[2] ..... a[n-1]

sumA = a[0] + a[1] +....+a[n-1]
sumASquare = a[0]*a[0] + a[1]*a[1] + a[2]*a[2] + .... + a[n]*a[n]

sumFirstN = (N*(N+1))/2 where N=n-3 so
sumFirstN = (n-3)(n-2)/2

similarly

sumFirstNSquare = N*(N+1)*(2*N+1)/6 = (n-3)(n-2)(2n-5)/6

Suppose repeated elements are = X and Y

so X + Y = sumA - sumFirstN;
X*X + Y*Y = sumASquare - sumFirstNSquare;

So on solving this quadratic we can get value of X and Y.
Time Complexity = O(n)
space complexity = O(1)

答案 9 :(得分:1)

由于指定了范围,因此您可以执行基数排序。这会将您的数组排序为O(n)。在排序的数组中搜索重复项是O(n)

答案 10 :(得分:1)

您可以使用简单的嵌套for循环

 int[] numArray = new int[] { 1, 2, 3, 4, 5, 7, 8, 3, 7 };

        for (int i = 0; i < numArray.Length; i++)
        {
            for (int j = i + 1; j < numArray.Length; j++)
            {
                if (numArray[i] == numArray[j])
                {
                   //DO SOMETHING
                }
            }

* 或者您可以过滤数组并使用递归函数,如果您想获得出现次数 *

int[] array = { 1, 2, 3, 4, 5, 4, 4, 1, 8, 9, 23, 4, 6, 8, 9, 1,4 };
int[] myNewArray = null;
int a = 1;

 void GetDuplicates(int[] array)
    for (int i = 0; i < array.Length; i++)
            {
                for (int j = i + 1; j < array.Length; j++)
                {
                    if (array[i] == array[j])
                    {
                          a += 1;
                    }
                }
                Console.WriteLine(" {0} occurred {1} time/s", array[i], a);

                IEnumerable<int> num = from n in array where n != array[i] select n;
                 myNewArray = null;
                 a = 1;
                 myNewArray = num.ToArray() ;

                 break;

            }
             GetDuplicates(myNewArray);

答案 11 :(得分:1)

回答18 .. 你得到一个9的数组,元素从0开始。所以你的数组中最大的ele将是6。获取0到6之间的元素总和并获取数组元素的总和。计算他们的差异(比方说d)。这是p + q。现在取0到6的元素的XOR(比如x1)。现在对数组元素进行异或(比如x2)。 x2是从0到6的所有元素的XOR,除了两个重复的元素,因为它们相互抵消。现在对于i = 0到6,对于每个数组的ele,假设p是ele a [i],所以你可以通过从d中减去这个ele来计算q。执行p和q的异或,并用x2对它们进行异或,并检查x1 == x2。同样为所有元素做,你将获得这个条件为真的元素,你在O(n)中完成。继续编码!

答案 12 :(得分:1)

这是在Python中实现@eugensk00的答案(其中一个修订版),它不使用模运算。它是单通算法, O(log(n))在空间。如果使用固定宽度(例如32位)整数,则仅需要两个固定宽度的数字(例如,对于32位:一个64位数和一个128位数)。它可以处理任意大整数序列(它一次读取一个整数,因此整个序列不需要在内存中)。

def two_repeated(iterable):
    s1, s2 = 0, 0
    for i, j in enumerate(iterable):
        s1 += j - i     # number_of_digits(s1) ~ 2 * number_of_digits(i)
        s2 += j*j - i*i # number_of_digits(s2) ~ 4 * number_of_digits(i) 
    s1 += (i - 1) + i
    s2 += (i - 1)**2 + i**2

    p = (s1 - int((2*s2 - s1**2)**.5)) // 2 
    # `Decimal().sqrt()` could replace `int()**.5` for really large integers
    # or any function to compute integer square root
    return p, s1 - p

示例:

>>> two_repeated([2, 3, 6, 1, 5, 4, 0, 3, 5])
(3, 5)

以上代码的更详细版本如下:

def two_repeated_seq(arr):
    """Return the only two duplicates from `arr`.

    >>> two_repeated_seq([2, 3, 6, 1, 5, 4, 0, 3, 5])
    (3, 5)
    """
    n = len(arr)
    assert all(0 <= i < n - 2 for i in arr) # all in range [0, n-2)
    assert len(set(arr)) == (n - 2) # number of unique items

    s1 = (n-2) + (n-1)       # s1 and s2 have ~ 2*(k+1) and 4*(k+1) digits  
    s2 = (n-2)**2 + (n-1)**2 # where k is a number of digits in `max(arr)`
    for i, j in enumerate(arr):
        s1 += j - i     
        s2 += j*j - i*i

    """
    s1 = (n-2) + (n-1) + sum(arr) - sum(range(n))
       = sum(arr) - sum(range(n-2))
       = sum(range(n-2)) + p + q - sum(range(n-2))
       = p + q
    """
    assert s1 == (sum(arr) - sum(range(n-2)))

    """
    s2 = (n-2)**2 + (n-1)**2 + sum(i*i for i in arr) - sum(i*i for i in range(n))
       = sum(i*i for i in arr) - sum(i*i for i in range(n-2))
       = p*p + q*q
    """
    assert s2 == (sum(i*i for i in arr) - sum(i*i for i in range(n-2)))

    """
    s1 = p+q
    -> s1**2 = (p+q)**2
    -> s1**2 = p*p + 2*p*q + q*q
    -> s1**2 - (p*p + q*q) = 2*p*q
    s2 = p*p + q*q
    -> p*q = (s1**2 - s2)/2

    Let C = p*q = (s1**2 - s2)/2 and B = p+q = s1 then from Viete theorem follows
    that p and q are roots of x**2 - B*x + C = 0
    -> p = (B + sqrtD) / 2
    -> q = (B - sqrtD) / 2
    where sqrtD = sqrt(B**2 - 4*C)

    -> p = (s1 + sqrt(2*s2 - s1**2))/2
    """
    sqrtD = (2*s2 - s1**2)**.5
    assert int(sqrtD)**2 == (2*s2 - s1**2) # perfect square
    sqrtD = int(sqrtD)
    assert (s1 - sqrtD) % 2 == 0 # even
    p = (s1 - sqrtD) // 2
    q = s1 - p
    assert q == ((s1 + sqrtD) // 2)
    assert sqrtD == (q - p)
    return p, q

注意:计算数字的整数平方根(~N ** 4)会使上述算法成为非线性算法。

答案 13 :(得分:1)

检查一下...... O(n)时间和O(1)空间复杂度

 for(i=0;i< n;i++)
 xor=xor^arr[i]
 for(i=1;i<=n-3;i++)
 xor=xor^i;

因此,在给定的示例中,您将获得3和5的xor

xor=xor & -xor  //Isolate the last digit

for(i = 0; i < n; i++)
{
if(arr[i] & xor)
  x = x ^ arr[i]; 
else
  y = y ^ arr[i]; 
}
for(i = 1; i <= n-3; i++)
{
if(i & xor)
  x = x ^ i; 
else
  y = y ^ i; 

}

x和y是你的答案

答案 14 :(得分:1)

对阵列进行排序似乎是最佳解决方案。一个简单的排序将使搜索变得微不足道,并且将花费更少的时间/空间。

否则,如果您知道数字的域,则创建一个包含许多桶的数组,并在您通过数组时递增每个数组。像这样的东西:

int count [10];

for (int i = 0; i < arraylen; i++) {
    count[array[i]]++;
}

然后只搜索你的数组中任何大于1的数字。这些是具有重复项的项目。只需要在原始数组中进行一次传递,然后一次遍历计数数组。

答案 15 :(得分:0)

这是一种使用订单统计信息并在O(n)中运行的算法。

您可以通过重复调用SELECT并将中位数作为参数来解决此问题。

你也依赖于在致电SELECT之后的事实, 小于或等于中位数的元素将移动到中位数的左侧。

  • 使用中位数作为参数调用SELECT上的A
  • 如果中值为floor(n/2),则重复值与中位数相符。所以你继续使用数组的右半部分。
  • 否则,如果不是这样,则重复值留给中位数。所以你继续使用数组的左半部分。
  • 你递归地继续这种方式。

例如:

  • A={2, 3, 6, 1, 5, 4, 0, 3, 5} n=9时,中位数应为值4
  • 第一次致电SELECT
  • 之后
  • A={3, 2, 0, 1, <3>, 4, 5, 6, 5}中值小于4所以我们继续左半边。
  • A={3, 2, 0, 1, 3}
  • 第二次致电SELECT
  • 之后
  • A={1, 0, <2>, 3, 3}然后中位数应为2,我们继续使用右半部分。
  • A={3, 3},找到。

此算法在O(n+n/2+n/4+...)=O(n)中运行。

答案 16 :(得分:0)

使用https://en.wikipedia.org/wiki/HyperLogLog怎么样?

Redis做http://redis.io/topics/data-types-intro#hyperloglogs

  

HyperLogLog是一种概率数据结构,用于计算唯一的事物(技术上,这是指估计一组的基数)。通常计算唯一项目需要使用与您想要计算的项目数量成比例的内存量,因为您需要记住过去已经看过的元素,以避免多次计算它们。但是,有一组算法可以交换内存以获得精确度:在Redis实现的情况下,您使用标准错误的估计度量结束,小于1%。这种算法的神奇之处在于您不再需要使用与计数项目数量成比例的内存量,而是可以使用恒定的内存量!在最坏的情况下为12k字节,如果你的HyperLogLog(我们从现在开始称它们为HLL)已经看到很少的元素,那么会少很多。

答案 17 :(得分:0)

使用嵌套的 for 循环并假设问题是找到在数组中只出现两次的数字。

def repeated(ar,n):
    count=0
    for i in range(n):
        for j in range(i+1,n):
            if ar[i] == ar[j]:
                count+=1
        if count == 1:
            count=0
            print("repeated:",ar[i])    

arr= [2, 3, 6, 1, 5, 4, 0, 3, 5]
n = len(arr)
repeated(arr,n)

答案 18 :(得分:0)

在c:

    int arr[] = {2, 3, 6, 1, 5, 4, 0, 3, 5};

    int num = 0, i;

    for (i=0; i < 8; i++)
         num = num ^ arr[i] ^i;

x^x=0起,重复奇数次的数字被抵消。让我们调用唯一的数字a和b。我们留下a^b。我们知道a^b != 0,因为a != b。选择任意1位a^b,并将其用作掩码,即选择x作为2的幂,以便x & (a^b)非零。

现在将列表拆分为两个子列表 - 一个子列表包含所有带有y&x == 0的数字y,其余的列表放在另一个子列表中。顺便说一下,我们选择x,我们知道a和b的对在不同的桶中。因此,我们现在可以独立地将上面使用的相同方法应用于每个存储桶,并发现a和b是什么。

答案 19 :(得分:0)

我写了一个小程序,找出没有重复的元素的数量,只是通过这个让我知道你的意见,此刻我假设偶数元素是偶数但也可以很容易地扩展为奇数。< / p>

所以我的想法是首先对数字进行排序,然后应用我的算法。快速排序可用于对这些元素进行排序。

让我们按照下面的输入数组

int arr[] = {1,1,2,10,3,3,4,5,5,6,6};

数字2,10和4不会重复,但是它们按排序顺序排列,如果没有排序则使用快速排序来首先排序。

让我们在这个

上应用我的程序
using namespace std;

main()
{
    //int arr[] = {2, 9, 6, 1, 1, 4, 2, 3, 5};
    int arr[] = {1,1,2,10,3,3,4,5,5,6,6};

    int i = 0;

    vector<int> vec;

    int var = arr[0];
    for(i = 1 ; i < sizeof(arr)/sizeof(arr[0]); i += 2)
    {
            var = var ^ arr[i];

            if(var != 0 )
            {
                //put in vector
                var = arr[i-1];
                vec.push_back(var);
                i = i-1;
            }
            var = arr[i+1];
    }

    for(int i = 0 ; i < vec.size() ; i++)
        printf("value not repeated = %d\n",vec[i]);

}

这给出了输出:

value not repeated= 2

value not repeated= 10

value not repeated= 4

简单直接,只需使用XOR man。

答案 20 :(得分:0)

for(i=1;i<=n;i++) {
  if(!(arr[i] ^ arr[i+1]))
        printf("Found Repeated number %5d",arr[i]);
}

答案 21 :(得分:0)

这个怎么样:

for (i=0; i<n-1; i++) {
  for (j=i+1; j<n; j++) {
    if (a[i] == a[j]) {
        printf("%d appears more than once\n",a[i]);
        break;
    }
  }
}

当然这不是最快的,但它简单易懂,而且要求 没有额外的记忆。如果n是9或100之类的小数,那么它可能是“最好的”。 (即“最佳”可能意味着不同的事情:执行速度最快,内存占用最小,可维护性最高,开发成本最低等。)

答案 22 :(得分:0)

如果没有排序,您将会跟踪已经访问过的数字。

在伪代码中,这基本上是(这样做,所以我不只是给你答案):

for each number in the list
   if number not already in unique numbers list
      add it to the unique numbers list
   else
      return that number as it is a duplicate
   end if
end for each

答案 23 :(得分:0)

对于每个数字:检查数组的其余部分是否存在。

答案 24 :(得分:-1)

我们为什么要尝试做数学(特别是求解二次方程),这些都是昂贵的算法。解决这个问题的最佳方法是构造一个大小(n-3)位的位图,即(n-3)+7 / 8字节。最好为此内存执行calloc,因此每个位都将初始化为0。然后遍历列表&amp;遇到时将特定位设置为1,如果该位已经设置为1则为no,那么就是重复的no。 这可以扩展到找出数组中是否有任何缺失no。 该解决方案的时间复杂度为O(n)