异或操作直觉

时间:2017-01-31 17:32:30

标签: c++ bit-manipulation

我最近遇到了关于Leetcode的this问题,并想出了一个我需要澄清的解决方案:

  

给定一个整数数组,除了一个元素外,每个元素都会出现两次。找到那一个。

     

注意:   您的算法应具有线性运行时复杂性。你可以在不使用额外内存的情况下实现吗?

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int result = 0;
        for(auto & c : nums) {
            result ^= c;
        }
        return result;
    }
};

首先,为了弄清楚我应该对这个问题使用XOR操作,我应该注意哪些类型的关键字?

另外,为什么对矢量中的所有项目相互异或,给我们一个不重复的项目?

谢谢大家对这些回复,以下是其他任何感兴趣的有关按位属性的更多信息:More bitwise info

7 个答案:

答案 0 :(得分:86)

  1. A ^ 0 == A

  2. A ^ A == 0

  3. A ^ B == B ^ A

  4. (A ^ B) ^ C == A ^ (B ^ C)

  5. (3)和(4)一起意味着数字xor的顺序并不重要。

    这意味着,例如,A^B^X^C^B^A^C等于A^A ^ B^B ^ C^C ^ X

    因为(2)等于0^0^0^X

    因为(1)等于X

    我不认为有任何特定的关键字可以帮助您识别此类问题。你应该知道XOR的上述属性。

答案 1 :(得分:21)

Xor运算符是可交换的

export class HomePageComponent implements OnInit {
  pageTitle: string = 'Gaming News';
  errorMessage: string;
  news: INews[];
  @ViewChild('bestNews')menuNews: ElementRef;

  constructor(private _newsData: NewsDataService,
          private elementRef: ElementRef,
          private renderer: Renderer) {}

  showNews(): void {
    this.renderer.setElementClass(this.menuNews, 'best-news-hidden' false);
  }

  ngOnInit(): void {
    this._newsData.getNews()
      .subscribe(news => this.news = news,
                 error => this.errorMessage = <any>error);
  }
}

associative

1.      X ⊕ Y = Y ⊕ X                    for any integers X and Y

因此,任何2. X ⊕ (Y ⊕ Z) = (X ⊕ Y) ⊕ Z for any integers X, Y and Z 运算序列的结果完全独立于操作数的顺序(即数组中元素的顺序)。

xor

在这个问题中我们有一个表达式,其中每个元素Ai出现两次,除了一些奇异元素B.结果Xor操作相当于:

3.     X ⊕ X = 0                         for any integer X

4.     X ⊕ 0 = 0 ⊕ X = X                for any integer X
  

我应该注意哪些类型的关键字,以便弄清楚我应该对此问题使用XOR操作

使用位操作可以快速解决一些问题。熟悉布尔运算符及其属性,并看到类似这样的应用程序之后,您自然会“感觉”它们何时对解决给定问题有用。

答案 2 :(得分:7)

区分 XOR 与其他逻辑运算符的关键直观方面是 无损 ,或无损< / em>,意思是,与 AND OR (在这方面更类似于 NOT )不同,它具有决定性的可逆性:你在给定剩余的计算历史记录的情况下,它可以准确地恢复其中一个输入值。

以下图表说明 AND OR 至少有一种情况,其中一个输入的状态是不可恢复的,给定另一个输入的某个值。我将这些指示为“丢失”输入。

AND and OR logical operators can lose information

对于 XOR 门,在给定剩余的计算历史记录的情况下,没有条件无法恢复输入或输出值。实际上,有一种对称性,即知道三元组(in0, in1, out)任意两个值可以让你恢复第三个值。换句话说,无论输入或输出如何,这三个值中的每一个都是其他两个值的 XOR

XOR logical operator does not lose information

这张照片表明,考虑 XOR 操作的另一种方法是作为可控NOT 门。通过切换其中一个输入(上例中的上一个输入),您可以控制是否其他(较低)输入被否定。

另一个等效观点是 XOR 针对其两个输入实现positive-logic not-equals (≠)函数。因此negative-logic下的等于函数(=)。

根据其对称性和信息保留属性,对于需要可逆性或完美数据恢复的问题,应该考虑 XOR 。最明显的例子是 XOR 一个带有常量'key'的数据集会使数据变得模糊不清,因此知道密钥(可能保持“秘密”)可以实现精确恢复。

hashing中也需要保留所有可用信息。因为您希望哈希值最大程度地区分源项目,所以您希望确保尽可能多地将其区分特征合并到哈希码中,从而最大限度地减少损失。例如,要将64位值散列为32位,您可以使用编程语言 XOR 运算符^,因为这是一种简单的方法来保证64个输入位中的每一个都有一个影响产出的机会:

uint GetHashCode(ulong ul)
{
    return (uint)ul ^ (uint)(ul >> 32); 
}

请注意,在此示例中,即使使用 XOR ,信息也会丢失。 (事实上​​,'战略信息丢失'是散列的重点)。 ul的原始值无法从哈希代码中恢复,因为仅使用该值,您就没有内部计算中使用的三个32位值中的两个。回想一下,您需要保留三个值中的任意两个以实现完美的反转。在生成的哈希码和 XOR ed的两个值之外,您可能已保存结果,但通常不保存后者中的任何一个以用作获取另一个的键值。 SUP> 1

除了有趣之外, XOR bit-twiddling hacks时代是独一无二的。我当时的贡献是在C / C ++中有条件地设置或清除位而没有分支的方法:

unsigned int v;       // the value to modify
unsigned int m;       // mask: the bits to set or clear
int f;                // condition: 0 to 'set', or 1 to 'clear'

v ^= (-f ^ v) & m;    // if (f) v |= m; else v &= ~m;

更严重的是,由于信息处理与{{3}之间的重要关系, XOR 是无损的这一事实对未来计算具有重要的information-theoretical影响。 }。正如Charles Seife撰写的一本优秀且易于理解的书Second Law of Thermodynamics所解释的那样,事实证明,计算过程中信息的丢失与Decoding the Universe的数据关系具有精确的数学关系。处理系统。事实上,black-body radiation的概念在量化信息“损失”如何(重新)表示为热量方面起着核心作用(这也与史蒂文霍金着名的entropy具有相同的显着关系)。 p>

关于 XOR 的这种谈话不一定是一个延伸; Seife指出,现代CPU开发目前面临着对瓦/cm²半导体材料的基本容忍限制,并且解决方案是设计可逆或无损计算系统。在这种推测性的未来一代CPU中, XOR 保存信息的能力 - 因此分流热量 - 对于提高计算密度非常有用(即{{3尽管存在这样的材料限制,但每平方厘米。


<小时/> 1。在这个简单的示例中,相关的3个值将是哈希码加上原始ulong值的上部和下部。当然,原始的散列'数据'本身,在此处由ul表示,可能 保留。

答案 3 :(得分:2)

XOR始终以二进制数字(或某些等效概念,如true或false语句)定义。除了二进制表示的相应位的异或之外,对于整数没有特殊的XOR。

让A和B成为两个布尔变量,让XOR成为一个带有两个布尔变量的布尔函数。
如果(A = 0和B = 1)或(A = 1和B = 0)(即它们不同),A⊕B= 1,
如果(A = 0和B = 0)或(A = 1且B = 1),则A⊕B= 0。 (即它们是相同的)

因此,考虑到向量的给定n个元素后,每个元素出现两次,除了一个元素,这个想法是 重复数字的二进制表示将是相同的,因此XOR结果将相互抵消为1⊕1= 0和0⊕0= 0.

对于A = 5,其二进制表示为101,因此A⊕A=(101)⊕(101)= 000,即十进制表示为0.

请记住,这并不重要,因为这些数字在每个其他地方之后出现((A⊕B)⊕C)=(A⊕(B⊕C))。 最终,在XORING之后你得到的每个数字都是一次出现的数字。

要回答您关于何时需要使用XOR操作来解决问题的问题,请练习一些BIT MANIPULATION问题,最终您将能够找到答案。
提示:要求找到一个除休息之外具有独特属性的元素的问题需要进行位操作 示例:给定一个数组,其中每个元素出现三次,除了一个仅出现一次的元素。找到一次出现的元素。

答案 4 :(得分:0)

可以对数组执行的一个简单操作是选择属性P并计算满足该属性的数组元素的数量。例如,如果P是可被5整除的属性,我们可以遍历数组并计算可被5整除的元素数。

数组的前提条件允许我们从这样的计数中获取有关单例元素的信息。如果满足P的元素数是奇数,则单例具有属性P。如果满足P的元素数是偶数,则单例不能具有属性P。例如,如果我们计算3个可被5整除的元素,那么单例必须可以被5整除。

因此,如果我们可以创建一个属性集合,以便知道每个属性是真还是假将完全指定任何元素,我们可以通过计算每个属性的元素数量并检查计数的奇偶校验来得到答案

有许多不同的属性集合可以使用。不过,我们可能也很有效率。给定b个不同的属性,测试可能会出现(最多)2**b种不同的方式,因此我们只能从这许多可能的元素中区分出来。因此,我们至少需要b个不同的属性来完全指定b - 位数。

一个易于计算机测试的最小属性集合是n属性为“n位数是否为1的集合?”。如果我们知道每个n的这个问题的答案,那么显然我们可以重建数字。

另一个优化是我们不必跟踪满足属性的元素总数;我们只需要平价。如果我们只是跟踪模2的计数,那么我们只需要一位来跟踪而不是整个size_t

由于我们正在跟踪b个不同的信息位,每个位对应一个特定的位值n,我们可以将这些位组合成一个b位数。

这正是问题中提出的XOR解决方案。

首先,每个位的每个位的数量的运行计数为0(因此result被初始化为0)。然后,当我们对数组中的一个元素进行异或时,我们实际上是将一个模2添加到result的那些位,其中元素具有1.最后,result不需要任何解码,因为数字正好在result有1位的1位是result本身。

答案 5 :(得分:0)

我相信你很熟悉,整数作为二进制数字的元组存储在内存中。可以在两个元素的字段中将每个数字视为一个数字,这基本上是模2的整数。^运算符是分量xor,并且通过这种解释,xor只是加法。也就是说,我们将二进制数字相互添加。

在此字段中,1 + 1 = 0,因此可以说两个为零。由于加法是可交换的和关联的,我们可以同时组合所有数字。任何添加偶数次的内容都不会增加任何内容,只有一次添加的数字最终会出现在结果变量中。

知道布尔运算以这种方式表示为(尝试它!)可能很有趣:a xor b = a + b, b = ab,a b = ab + a + b, a = a + 1。

答案 6 :(得分:0)

  • 已经发布了几个好的答案。
  • 关于您的问题,该注释非常有帮助:

注意:您的算法应具有线性运行时复杂度。可以 您可以在不使用额外内存的情况下实现它?

  • 它表示在线性时间上需要恒定的内存,这意味着我们将至少访问一次数组的每个元素,然后我们只需要某种类型的数学运算即可解决问题:

这是一个使用数学的解决方案,但有额外的空间:

Python

class Solution:
    def singleNumber(self, nums):
        return (sum(set(nums)) << 1) - sum(nums)

以下是按位解决方案:

C ++

// The following block might slightly improve the execution time;
// Can be removed;
static const auto __optimize__ = []() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    return 0;
}();

// Most of headers are already included;
// Can be removed;
#include <cstdint>

using ValueType = std::uint_fast16_t;

static const struct Solution {
    static const int singleNumber(
        const std::vector<int> nums
    ) {
        ValueType single_number = 0;

        for (ValueType index = 0; index < std::size(nums); index++) {
            single_number ^= nums[index];
        }

        return single_number;
    }
};

Java


public final class Solution {
    public static final int singleNumber(
        final int[] nums
    ) {
        int singleNumber = 0;

        for (int index = 0; index != nums.length; index++) {
            singleNumber ^= nums[index];
        }

        return singleNumber;
    }
}