在这个函数中解释XOR的这种用法?

时间:2011-09-16 15:43:35

标签: c#

我看到了这段代码片段,用于解决“在数组中找到一个没有重复的数字”的问题。题。今天早上我一直在看这个问题,但是不能确切地说明它是怎么做的。

我不明白k总是如何保持非重复的值。任何人都可以解释这是如何工作的吗?

  static void Main(string[] args)
  {
     int[] list = { 3,6,9,12,3,6,9 };
     int k = 0;


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

     Console.WriteLine(k);
  }

8 个答案:

答案 0 :(得分:12)

仅当一个数字不重复(或发生任何奇数次)且所有其他数字出现偶数次时才有效。

如果您将一个号码与另一个号码x或两次(或任何其他偶数次),它会自行取消原始号码。

答案 1 :(得分:7)

它与"Nerds, Jocks and Lockers" problem略有相似之处,就是“位翻转”会使某些位置位而其他位置不会。

基本行为是A XOR B的作用类似于“(A OR B)和NOT(A和B)”。因此,0 ^ 0 = 0,1 ^ 0 = 1,但是1 ^ 1 = 0(与OR不同)。现在,你从K开始为零(没有设置位)。然后用文字3对其进行异或,其中(作为一个字节)有00000011位,并将结果赋值给K.你最终得到00000011为K,因为文字3上设置的位在0时都没有设置在K上。现在,如果你再次使用文字3对XOR K进行异常,你最终会得到0,因为这两个值之间的所有位都匹配,所以XOR会在每个位上返回0。

这个过程是可交换的,所以(((((0 XOR 3)XOR 6)XOR 3)XOR 6)得到与((((0 XOR 6)XOR 6)XOR 3)XOR相同的结果(0) 3),或几乎任何其他组合的XORing 0与3两次和6两次。

最终结果是,给定这些数字的列表,任何出现两次(或偶数次)的数字在第一次被“异或”到K,然后“异常”出第二次,离开K将其位设置为仅发生一次的一个值; 12。

这是完整问题的二进制演示(使用“半字节”,因为我们没有超过16的任何值):

0000 0
^^^^ XOR
0011 3
---- =
0011 3
^^^^ XOR
0110 6
---- =
0101 5
^^^^ XOR
1001 9
---- =
1100 12
^^^^ XOR
1100 12
---- =
0000 0 <-this is coincidence; it'd work the same regardless of the unduped value
^^^^ XOR
0011 3
---- =
0011 3
^^^^ XOR
0110 6
---- =
0101 5
^^^^ XOR
1001 9
---- =
1100 12 <- QED

从评论中编辑:虽然这个答案适用于所提出的具体问题,但即使对问题的最小改动也会“破坏”此实施,例如:

  • 该算法完全没有响应数字;因此,该算法无法区分将零作为单个未配对值与根本没有未配对值之间的区别。
  • 该算法仅适用于对,而非三元组。如果3次发生三次并且仍然是“欺骗”,而12仍然是正确答案,那么该算法实际上将返回15(1100 ^ 0011 == 1111),这甚至不在列表中。
  • 该算法仅在列表中只有一个非重复值时才有效;如果8和12都是预期返回的未配对值,算法将返回两者的XOR(1100 ^ 1000 == 0100 == 4)

除了原始案例之外,还可以开发一种在所有这些情况下都能返回正确答案的有效算法,但它可能不会涉及异或。

答案 2 :(得分:1)

任何数字都可以表示为位序列:

3 == 0...00011
6 == 0...00110
9 == 0...01001

如果您对它们进行两次异或,则位切换将相互抵消。

因此,如果单个号码在列表中出现一次(或奇数次),则它将是唯一一个位为“未取消”的位。

答案 3 :(得分:1)

keithS是现货,但这可能更容易理解。

我能想到的最简单的解释与XOR的四个属性有关:

  1. 0 XOR x = x,任何数字x
  2. x XOR x = 0,任意数字x
  3. XOR是可交换的:x XOR y = y XOR x(这允许您重新排列一系列XOR操作,但是您认为合适)
  4. XOR是关联的:(x XOR y)XOR z = x XOR(y XOR z)(这允许您按照您认为合适的任何顺序评估XOR)
  5. 通过属性2和3,您可以重新排列输入列表,以便所有重复项彼此相邻:

    {3,6,9,12,3,6,9} - &gt; {3,3,6,6,9,9,12};

    通过属性1和4,我们可以按任意顺序对这些数字进行异或,并且所有相同的数字对变为0.此后,只有非重复数字保持非零:

    {0,0,0,12};

    同样由属性1,因为k开始时为0,并且所有重复的数字都被异或变为零,所以剩下的就是12

答案 4 :(得分:1)

这是有效的,因为XOR运算符同时是commutativeassociative。这意味着您可以按任何顺序重新排列条款:

a ^ b ^ c == a ^ c ^ b == c ^ a ^ b == ... (etc.)

这适用于任何长度的序列。因此:

3 ^ 6 ^ 9 ^ 12 ^ 3 ^ 6 ^ 9 == (3 ^ 3) ^ (6 ^ 6) ^ (9 ^ 9) ^ 12

对于任何整数都是x ^ x == 0,这意味着您可以消除所有对,直到获得0 ^ 12,这等于12

答案 5 :(得分:0)

a ^ a = 0
a ^ 0 = a
0 ^ a = a ^ 0
a = b ^ c
a = c ^ b

int[] list = { 3,6,9,12,3,6,9 };

k ^ 3
k ^ 6
k ^ 9
k ^ 12
k ^ 3
k ^ 6
k ^ 9

=

k ^ 3 // dupe
k ^ 3 
k ^ 6
k ^ 6
k ^ 9
k ^ 9
k ^ 12 // non-dupe

=

k ^ 12

=

0 ^ 12

=

12

答案 6 :(得分:0)

您引用的代码实际上并没有解决问题。例如,在列表中放入三个12,即使12个重复两次,您也会获得12个。

XOR的结果实质上是该位组合是奇数还是偶数。因此,如果列表包含奇数个12(无论是一个12或一百一十二个)和每个其他数字的偶数,结果将始终为12。

更糟糕的是,如果列表包含奇数个多个不同的数字,结果值可能不是列表中的任何数字。例如,一个3和一个14将导致13.这是因为XOR实际上是对位而不是整数进行操作。 14(1110b)和3(0011b)的XOR导致13(1101b),因为XOR将数字之间的任何位设置为零。

实际解决问题的代码:

using System.Linq;
using System.Collections.Generic;
...
static void Main ( string[] args)
{
    int[] list = { 3,6,9,12,3,6,9 };
    int[] nonDupes = list
        .GroupBy(x => x)
        .Where(x => x.Count() == 1)
        .Select(x => x.Key)
        .ToArray();
    string output = string.Join(",", nonDupes);
    Console.WriteLine(output);
}

答案 7 :(得分:0)

使用XOR时,您必须记住您将使用位。换句话说,你只有0和1来处理。

XOR的定义非常简单:

0 XOR 0 -> 0
0 XOR 1 -> 1
1 XOR 1 -> 0

因此,如果您将此应用于您的代码并将3转换为 0011 ,将6转换为 0110 ,将9转换为 1001 以及12到 1100 ,并在它们上面调用XOR方法,就像在下面的例子中一样,你将得到 1100 ,它将代表值12。

示例

    0011
XOR
    0110
=
    0101 -> 5

这不是消除重复值的算法。巧合的是,你获得了12。