找到三个号码只出现一次

时间:2010-06-09 04:02:57

标签: algorithm bit-manipulation sequence

在长度为n的序列中,其中n = 2k + 3,即有两个k唯一数字出现 三个号码只出现一次。

问题是:如何查找仅出现一次的三个唯一数字?

例如,在顺序1 1 2 6 3 6 5 7 7中,三个唯一数字是2 3 5。

注意: 3< = n< 1e6,数量范围为1至2e9

内存限制:1000KB,这意味着我们无法存储整个序列。

我尝试过的方法(内存限制超过):

我初始化一个树,当读入一个数字时,我尝试将其从树中删除,如果是  remove返回false(未找到),我将它添加到树中。最后,树有三个数字。它有效,但内存限制超过。

我知道如何使用位操作找到一个或两个这样的数字。所以我想知道是否

我们可以找到三个使用相同的方法(或类似的方法)?

查找一个/两个号码的方法只出现一次:

如果只有一个数字出现一次,我们可以对序列应用XOR来找到它。

如果有两个,我们可以先对序列应用XOR,然后将序列分成2 将结果的一部分分成1,然后再将XOR应用于2部分,我们将找到答案。

6 个答案:

答案 0 :(得分:9)

对于这个问题的更一般版本(没有那些愚蠢的限制):

你可以在O(n)时间和O(1)空间中执行此操作,不用假设任何边界,或迭代覆盖所有位和< / em>仅使用O(1)时间位操作技巧,如XOR技巧,可用于2个缺失的数字。

这是(伪)代码,只能找到其中一个数字:

// Given an array arr with 2k+3 numbers, k of which are repeated twice
// and the remaining three are distinct: a,b,c.
// returns one of a,b,c.
int FindUnique(int []arr) {

    int s = 0; // This will ultimately hold a ^ b ^ c (bitwise XOR)

    for (int i = 0; i < arr.Length; i++) {
        s ^= arr[i];
    }

    int d = 0; // this holds diff(a,s) ^ diff(b,s) ^ diff(c,s)

    for (int i = 0; i < arr.Length; i++) {
        d ^= diff(arr[i],s);
    }

    int e = lowestBit(d); // This gives the position where one of a,b,c differs 
                          // from the others.

    int bucket1 = 0;
    int bucket2 = 0;

    for (int i = 0; i < arr.Length; i++) {
        if (arr[i] & e) {
            bucket1 ^= arr[i];
        } else {
            bucket2 ^= arr[i];
        }
    }

    int count1 = 0;
    int count2 = 0;

    for (int i = 0; i < arr.Length; i++) {
        if (arr[i] == bucket1) {
            count1++;
        }

        if (arr[i] == bucket2) {
            count2++;
        }
    }

    if (count1 == 1) return bucket1;

    return bucket2;
}

// return a number with the lowest bit of x ^ s set to 1 and rest 0.
// i.e. the lowest bit position where x and s differ.
int diff(int x, int s) {
    return lowestBit(x ^ s);
}

// Returns a number with only the lowest bit of y set.
int lowestBit(int y) {
    return y & ~(y-1);
}

这个想法如下:

说出一次出现的数字是a,b,c。

现在通过数组运行XOR以获得s = a XOR b XOR c。

由于数字不同,请注意s不能是a或b或c(因为其他两个将等于),因此至少有一个位(不一定在同一位置),其中每个a,b,c与s不同。

在这两个数字的情况下,我们可以看到s非零并选择一个区分a&amp; b并与之合作。

当我们有三个数字时,我们遇到了困难,但我们仍然可以找到一点来区分其中一个数字。

对于每个数字x,找到与s不同的最低位。考虑二进制数,其中只有该位设置为1,其余位为零。将此数字称为diff(x)。

现在,如果我们为每个数字计算diff(x)并将它们一起XOR,我们得到d = diff(a)XOR diff(b)XOR diff(c)。

请注意,d不能为零。

现在找到d的最低设置位。这个位位置可以用来清空a,b,c中的一个,因为不是所有的a,b,c都可以在该位置具有相同的位:如果它们这样做,那么s的那个位是那些位的XOR三个必须是相同的,但我们确保我们选择s的这一位与a,b,c中的至少一个相应位不同。

所以我们再次XOR,区分这个位,并检查两个结果数中的哪一个在数组中恰好出现一次。一旦找到一个数字,我们就知道如何处理两个数字。

要找到差异,只需使用bithack:x & ~(x-1),这是一个标准的位攻击,可以被认为是O(1)(而不是O(位数))。

答案 1 :(得分:7)

这是一个经典问题 - 几周前我实际上只是问过它。要解决这个问题,您需要获取可能出现的可能不同数字的数量,并分配那么多位。

例如,如果列表中的数字必须介于1-20之间,则分配20位 - 每个数字一位,并将每个位初始化为0。

然后你遍历列表。每次看到一个数字,都要翻转相应的位。

例如:使用您的示例列表2 6 3 6 5 7 7,我们可以分配7位(1 2 3 4 5 6 7)。然后,当我们遍历列表时,我们将执行以下操作:

  • 翻转第二位
  • 翻转第6位
  • 翻转第3位
  • 翻转第6位

然后,当您完成遍历列表后,您可以通读这些位来查找三个唯一的数字。它们都将由'1'位表示,其他数字将由0表示。

你读了两次列表,这需要2n时间,即O(n)。


编辑:可能不会给出界限。因此,一种解决方案是首先简单地读取列表以自己确定边界 - 然后它仍然是O(n)。

然而,可能出现的一个问题是列表可能非常小,但是一些非常大的数字 - 有效地使得范围太大。例如:

1, 99999999999999999, 1, 99999999999999999, 2, 3, 4

由于列表中存在大量数据,解决该问题需要大量内存,因为即使数字很少,范围也非常大,我们根据范围分配位。

然后可以使用哈希表调整解决方案以提供如下的新解决方案(尽管我不确定这是否允许这是因为问题的“仅位操作”规定):

  1. L表示原始列表,C表示其副本。
  2. C中删除所有重复项(有很多方法可以有效地执行此操作)。
  3. 创建哈希表H,并为C中的每个元素插入一个键/值对&lt; numberpos&gt;进入H,其中numberC中的当前元素,posC中的位置。因此,根据L中显示的数字,我们现在可以使用HC中找到该号码的位置。
  4. 分配等于C大小的位数,并将这些位初始化为0.
  5. Traverse L。每次我们在一个数字上运行时,从H获取其值,并在我们的位列表中翻转该位。
  6. 遍历位列表 - 对于每个“1”位,从位于该位置的C获取数字 - 这是唯一数字之一。

答案 2 :(得分:7)

您可以采用与一个和两个不同值的简单情况类似的方式执行此操作。

我们需要每个数字位的两个整数(例如32位)。对于每个数字,如果该位为零,则对其进行XOR第一个整数。如果不是,则用它与第二个整数进行异或。

另外,记住你在每个位置找到1或0的次数(我们只需要检查它是偶数还是奇数,所以保持一个布尔值。)

在迭代之后,我们的整数对将是以下之一。这里的第一个数字表示偶数,第二个数字表示奇数。

0, a^b^c
a^b, c
a^c, b
b^c, a

对于每对,检查偶数整数。如果它为零,那么我们知道另一个整数是^ b ^ c,因为我们的结果中没有两个是相等的。否则,我们在奇数计数整数处找到了一个值。

public static int[] find3(int[] list) {
    int[][] xors = new int[32][2];
    boolean[] counts = new boolean[32];
    for (int curr : list) {
        for (int i = 0; i < 32; i++) {
            xors[i][(curr & (1 << i)) >> i] ^= curr;
            counts[i] ^= ((curr & (1 << i)) == (1 << i));
        }
    }

    // this really shouldn't take so many lines
    int[] ret = new int[3];
    int found = 0;
    for (int i = 0; i < 32; i++) {
        int oddCount = xors[i][counts[i] ? 1 : 0];
        int evenCount = xors[i][counts[i] ? 0 : 1];
        if (evenCount != 0) { // avoid the 0, a^b^c case.
            if (found == 0) {
                ret[0] = oddCount;// a
                ret[2] = evenCount;// b^c for now
                found++;
            } else if (found == 1 && ret[0] != oddCount) {
                ret[1] = oddCount;// b
                ret[2] ^= oddCount;// (b^c)^b == c
                break;
            }
        }
    }
    return ret;
}

答案 3 :(得分:6)

如果概率解决方案足够,那么您可以使用Bloom Filter

创建两个Bloom过滤器。第一个(A)包含至少找到一个的数字,第二个(B)包含已经找到两次的数字。

伪代码:

A = empty
B = empty

foreach x in the list
  if x in A
    add x to B
  else
    add x to A

foreach x in the list
  if x in A
    if !(x in B)
      print x

如果您使用完整的1000KB,则错误概率可能会非常低。

答案 4 :(得分:1)

随着您添加更多唯一值,问题变得越来越困难,主要是因为您可以选择A,B,C,使得A xor B xor C = 0.如果值的子集已经变得越来越难以检测相同的校验和,因为它包含所有唯一值,或者因为它省略了xor到0的值。

您可以在常量空间和O(n * k)时间内执行3个值,其中k是最大整数中的位数。 (所以O(n)时间适用于典型情况:32位整数。)

如果唯一值的数量增加并且您继续需要恒定的空间,那么在N中找出时间界限是否变为非线性将会很有趣。

//Special check for 0, because otherwise we don't know A xor B xor C != A xor B
if items unique-contains 0 then
    return 0 ++ SubProblem2Unique(items - 0)
//Compute A xor B xor C
val x = fold xor items
//Try to find a split which separates A and B from C.
for i in 0..WORD_SIZE
    //see if the checksum splits
    val x1 = fold xor [e in items where e & (1<<i) == 0]
    val x2 = x xor x1
    if x1 == x or x2 == x then continue //ith bit was the same for A and B and C
    //C is either x1 or x2
    val C = if items unique-contains x1 then x1 else x2
    return C ++ SubProblem2Unique(items - C)

throw InvalidInput

答案 5 :(得分:0)

为什么不使用hashset? - 如果已存在数字,请从hashset中删除 - 如果数字不存在,则放入hashset 最终的hashset只包含唯一的数字。 时间:O(n) 记忆:o(k)其中k是不同元素的数量。

使用hashset方法,该解决方案具有可扩展性,可用于确定任何给定序列中的任意数量的唯一元素。