在数组中查找元素,其中每个元素重复奇数次(但多于单次出现),并且只出现一次

时间:2011-09-07 17:33:25

标签: arrays algorithm

你有一个数组,其中每个数字都重复奇数次(但多次出现)。恰好一个数字出现一次。你怎么找到只出现一次的号码?

e.g.: {1, 6, 3, 1, 1, 6, 6, 9, 3, 3, 3, 3}

答案是9。

我正在考虑使用哈希表,然后只计算计数为1的元素。 这似乎是微不足道的,我并没有使用这样一个事实,即每隔一个元素重复一次奇怪的事情。有没有更好的方法。

5 个答案:

答案 0 :(得分:38)

我相信您仍然可以使用XOR的基本思想以巧妙的方式解决这个问题。

首先,让我们更改问题,以便一个数字出现一次,所有其他数字出现三次。


算法:

此处A是长度为n的数组:

   int ones = 0;  
   int twos = 0;  
   int not_threes, x;  

   for (int i=0; i<n; ++i) {  
            x =  A[i];  
            twos |= ones & x;  
            ones ^= x;  
            not_threes = ~(ones & twos);  
            ones &= not_threes;  
            twos &= not_threes;  
   }  

恰好出现一次的元素存储在ones中。这使用O(n)时间和O(1)空格。

我相信我可以将这个想法扩展到问题的一般情况,但是可能你们中的一个可以更快地完成它,所以我现在就离开它并在我可以推广解决方案时进行编辑。


说明:

如果问题是这样:“一个元素出现一次,其他所有元素出现偶数次”,那么解决方案就是对元素进行异或。原因是x^x = 0,所以所有成对的元素都会消失,只留下孤独的元素。如果我们在这里尝试相同的策略,我们将留下不同元素的XOR,这不是我们想要的。

相反,上面的算法执行以下操作:

  • ones是到目前为止只出现过一次的所有元素的XOR
  • twos是到目前为止两次出现的所有元素的XOR

每次我们将x作为数组中的下一个元素时,有三种情况:

  1. 如果这是x第一次出现,则与ones
  2. 进行异或
  3. 如果这是x第二次出现,则会从ones中删除(再次对其进行异或)并将其转为twos
  4. 如果这是x第三次出现,则会从onestwos中删除。
  5. 因此,最后,ones将只是一个元素的异或,一个不重复的孤独元素。我们需要查看5行代码,以了解其工作原理:x = A[i]之后的5行。

    如果这是x第一次出现,那么ones&x=ones使twos保持不变。已声明ones ^= x;行与x的{​​{1}}行。因此,ones恰好是xones中的一个,因此在twosones的最后三行中没有任何内容。

    如果这是twos第二次出现,那么x已经有ones(通过上面的解释),所以现在x获取了twostwos |= ones & x; {1}}。此外,由于onesx,行ones ^= x;会从x中移除ones(因为x^x=0)。再一次,最后三行没有任何结果,因为onestwos中只有一行现在有x

    如果这是x第三次出现,那么ones没有xtwos没有。因此第一行让我们twos保持x,第二行将x添加到ones。现在,由于onestwos都有x,最后三行会从两者中删除x


    概括

    如果某些数字出现5次,则此算法仍然有效。这是因为第4次出现x,它既不是ones也不是twos。然后前两行将x添加到ones而不是twos,最后三行不执行任何操作。第5次出现x,它位于ones但不是twos。第一行将其添加到twos,第二行将其从ones中删除,最后三行不执行任何操作。

    问题在于第6次出现x,它来自onestwos,因此会在第7次传递时被添加回ones。我试图想出一个聪明的方法来防止这种情况,但到目前为止,我已经空了。

答案 1 :(得分:21)

对于所述的问题,最有效的答案是O(n)空间答案。另一方面,如果我们将问题缩小为“所有数字出现n次,除了只出现一次”或“甚至”所有数字出现n倍的倍数,除了只出现一次的那个“然后有一个相当简单的任何n(显然大于1)的解决方案,它只占用O(1)空间,即将每个数字分成多个位,然后计算每个位被打开的次数并取模数n。如果结果为1,则应在答案中打开它。如果为0,则应关闭它。 (任何其他答案显示问题的参数不成立)。如果我们检查n为2的情况,我们可以看到使用XOR就是这样(按位加法模2)。我们只是概括性地为n的其他值按位加法模数。

顺便说一下,这是n = 3的另一个答案,它实际上只是一种复杂的逐位加法方式,它为每个位存储一个2位计数。名为“ones”的int包含count的1位,而名为“twos”的int包含count的两位。 int not_threes用于在计数达到3时将两个位都设置回零,从而计算模3的位而不是正常的位(由于位将环绕,因此将是模4)。理解他的代码的最简单的方法是作为一个2位累加器,有一个额外的部分使它以模3运行。

因此,对于除了一个唯一编号之外所有数字出现3次的情况,我们可以为32位整数编写以下代码:

int findUnique(int A[], int size) {
  // First we set up a bit vector and initialize it to 0.
  int count[32];
  for (int j=0;j<32;j++) {
    count[j] = 0;
  }

  // Then we go through each number in the list.
  for (int i=0;i<size;i++) {
    int x = A[i];

    // And for each number we go through its bits one by one.
    for (int j=0;j<32;j++) {
      // We add the bit to the total.
      count[j] += x & 1;
      // And then we take it modulo 3.
      count[j] %= 3;
      x >>= 1;
    }
  }

  // Then we just have to reassemble the answer by putting together any
  // bits which didn't appear a multiple of 3 times.
  int answer = 0;
  for (int j=31;j>=0;j--) {
    answer <<= 1;
    if (count[j] == 1) {
      answer |= 1;
    }
  }

  return answer;
}

这段代码比其他答案略长(由于额外的循环,表面看起来更复杂,但它们每个都是恒定的时间),但希望更容易理解。显然,我们可以通过更密集地打包来减少内存空间,因为我们从来没有对计数中的任何数字使用超过两个。但我没有费心去做,因为它对渐近复杂性没有影响。

如果我们希望更改问题的参数,以便将数字重复5次,我们只需将3s更改为5s。或者我们也可以为7,11,137,727或任何其他数字(包括偶数)做同样的事情。但是不使用实际数字,我们可以使用它的任何因子,因此对于9,我们可以将其保留为3,对于偶数,我们可以使用2(因此只使用xor)。

然而,对于原始问题,没有一般的基于比特计数的解决方案,其中数字可以重复任何奇数次。这是因为即使我们在不使用模数的情况下完全计算位数,当我们查看特定位时,我们根本无法知道它出现的9次是代表3 + 3 + 3还是1 + 3 + 5。如果是打开三个不同的数字,每个出现三次,然后在我们的答案中应该关闭它。如果它出现在一个出现一次的数字,一个出现三次的数字,一个出现五次的数字,那么它应该在我们的答案中打开。但是只有位数,我们就不可能知道这一点。

这就是为什么另一个答案没有概括,并且处理特殊情况的聪明想法不会实现:任何基于逐位查看事物以确定哪些位应该打开或关闭的方案呢没有概括。鉴于此,我认为任何占用空间O(1)的方案都不适用于一般情况。有可能有巧妙的方案使用O(lg n)空间等,但我会怀疑它。我认为O(n)空间方法可能是所提出的问题中可以做到的最好的方法。我无法证明这一点,但在这一点上,这是我的直觉告诉我的,我希望我至少让你相信对“偶数”技术的小调整不会削减它。

答案 2 :(得分:2)

我知道这个问题的潜台词是找到一个有效或高效的解决方案,但我认为最简单,可读的代码很重要,在大多数情况下它已经足够了。

那怎么样:

var value = (new [] { 1, 6, 3, 1, 1, 6, 6, 9, 3, 3, 3, 3, })
    .ToLookup(x => x)
    .Where(xs => xs.Count() == 1)
    .First()
    .Key;

好老LINQ。 : - )

答案 3 :(得分:0)

使用c#测试分数100%

using System;
using System.Collections.Generic;
// you can also use other imports, for example:
// using System.Collections.Generic;

// you can write to stdout for debugging purposes, e.g.
// Console.WriteLine("this is a debug message");

class Solution {
    public int solution(int[] A) {

        Dictionary<int, int> dic =new Dictionary<int, int>();

        foreach(int i in A)
        {
            if (dic.ContainsKey(i))
            {
                dic[i]=dic[i]+1;                
            }
            else
            {
                dic.Add(i, 1);                
            }
        }

        foreach(var d in dic)
        {
            if (d.Value%2==1)
            {
                 return d.Key;
            }
        }

        return -1;
    }
}

答案 4 :(得分:0)

Java,正确性100%,性能100%,任务分数100%

// you can also use imports, for example:
// import java.util.*;

// you can write to stdout for debugging purposes, e.g.
// System.out.println("this is a debug message");
import java.util.HashMap;

class Solution {

     /*Simple solution 
          Will be using HashMap(for performance) as Array, 
          only Key set is needed.
          Initially tried with ArryList but there was performance issue with
          that so switch to HashMap.
          Iterate over the given array and if item is there in key set         
          remove it(coz you found your pair) otherwise add as new Key.
          After full Iteration only one key will be there in the Map that is 
          your solution.
          In Short: If pair is found, remove it from Map's Key set,
          Last Key will be your solution
    */
    public int solution(int[] A) {

        //Map but used as Array
        final HashMap<Integer, Boolean> mapHave = new HashMap<>();

        //Iterate over given Array
        for (int nIdx = 0; nIdx < A.length; nIdx++) {

            //Current Item
            Integer nVal = A[nIdx];

            //Try to remove the current item, if item does not exists will
            //return null and if condition will be true
            if (mapHave.remove(nVal) == null) {
                //current item not found, add it
                mapHave.put(nVal, Boolean.TRUE);
            }
        }

        //There will be only one key remaining in the Map, that is
        //your solution
        return mapHave.keySet().iterator().next();
    }
}