利用“异或”处理数组的相关算法的几个例子

例1,寻找数组中丢失的数。。。

         有一组数字,从1到n减少了一个数,顺序也被打乱了,放在一个n-1的数组里,请找出丢失的数字。

          在上一篇“数组公式相关算法”里介绍过一些解法,不过,那样解的话,可能会有溢出的危险。我们可以利用位运算中的“异或” 来巧妙解决这个问题。

          算法步骤:1,对1-n个数做异或运算,得到XOR = 1^2^3^4....^n。

                          2,   用XOR与当前n-1数组的所有元素依次取异或:

                                因为XOR中与当前数字相同的数,都在异或运算中抵消掉了,最终剩下的,就是我们要找的那个丢失的数。


所谓抵消:举个例子: 1,1,2

1: 0001

2:   0010                   1^1=0000               1^1^2 ===== 0000 ^ 0010 = 0010=2,也就是说两个1抵消了。

任何一个数字异或它自己都等于0,而0与任何数字求异或,都为原数字!


    

                   


例2,找出数组中两个只出现一次的数字

        题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)

        

================== 以下全部引自原文 =======================

分析:这是一道很新颖的关于位运算的面试题。

首先我们考虑这个问题的一个简单版本:一个数组里除了一个数字之外,其他的数字都出现了两次。请写程序找出这个只出现一次的数字。

这个题目的突破口在哪里?题目为什么要强调有一个数字出现一次,其他的出现两次?我们想到了异或运算的性质:任何一个数字异或它自己都等于0。也就是说,如果我们从头到尾依次异或数组中的每一个数字,那么最终的结果刚好是那个只出现1次的数字,因为那些出现两次的数字全部在异或中抵消掉了

有了上面简单问题的解决方案之后,我们回到原始的问题。如果能够把原数组分为两个子数组。在每个子数组中,包含一个只出现一次的数字,而其他数字都出现两次。如果能够这样拆分原数组,按照前面的办法就是分别求出这两个只出现一次的数字了。

我们还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。因为其他数字都出现了两次,在异或中全部抵消掉了。由于这两个数字肯定不一样,那么这个异或结果肯定不为0,也就是说在这个结果数字的二进制表示中至少就有一位为1。我们在结果数字中找到第一个为1的位的位置,记为第N位。现在我们以第N位是不是1为标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第N位都为1,而第二个子数组的每个数字的第N位都为0

现在我们已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其他数字都出现了两次。因此到此为止,所有的问题我们都已经解决。

基于上述思路,我们不难写出如下代码:

///

// Find two numbers which only appear once in an array

// Input: data - an array contains two number appearing exactly once,

//               while others appearing exactly twice

//        length - the length of data

// Output: num1 - the first number appearing once in data

//         num2 - the second number appearing once in data

///

void FindNumsAppearOnce(int data[], int lengthint &num1int &num2)

{

      if (length < 2)

            return;

 

      // get num1 ^ num2

      int resultExclusiveOR = 0;

      for (int i = 0; i < length; ++ i)

            resultExclusiveOR ^= data[i];

 

      // get index of the first bit, which is 1 in resultExclusiveOR

      unsigned int indexOf1 = FindFirstBitIs1(resultExclusiveOR);

 

      num1 = num2 = 0;

      for (int j = 0; j < length; ++ j)

      {

            // divide the numbers in data into two groups,

            // the indexOf1 bit of numbers in the first group is 1,

            // while in the second group is 0

            if(IsBit1(data[j], indexOf1))

                  num1 ^= data[j];

            else

                  num2 ^= data[j];

      }

}

 

///

// Find the index of first bit which is 1 in num (assuming not 0)

///

unsigned int FindFirstBitIs1(int num)

{

      int indexBit = 0;

      while (((num & 1) == 0) && (indexBit < 32))

      {

            num = num >> 1;

            ++ indexBit;

      }

 

      return indexBit;

}

 

///

// Is the indexBit bit of num 1?

///

bool IsBit1(int numunsigned int indexBit)

{

      num = num >> indexBit;

 

      return (num & 1);

}

原文地址:http://zhedahht.blog.163.com/blog/static/2541117420071128950682/

例3,腾讯10.09测试笔试题:有N+2个数,N个数出现了偶数次,2个数出现了奇数次(这两个数不相等),问用O(1)的空间复杂度,找出这两个数,不需要知道具体位置,只需要知道这两个值。

(@Rojay:xor一次,得到2个奇数次的数之和x。第二步,以x(展开成二进制)中有1的某位(假设第i位为1)作为划分,第二次只xor第i位为1的那些数,得到y。然后x xor y以及y便是那两个数。 


                                                            

是不是对 “异或” 是啥有点疑惑或者记不清了?,那好,

异或,也就是XOR  

        1、异或是一个数学运算符。他应用于逻辑运算。 其运算法则为a异或b=a'b+ab'(a'为非a)。 
  2、例如:真异或假的结果是真,假异或真的结果也是真,真异或真的结果是假,假异或假的结果是假。就是说两个值不相同,则异或结果为真。反之,为假。  
  异或运算法则:  
  1. a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c; 
  2. d = a ^ b ^ c 可以推出 a = d ^ b ^ c. 
  3、在计算机应用中,普遍运用,异或的逻辑符号 ^ (Shift + 6).形象表示为: 
  真^假=真  
  假^真=真  
  假^假=假 
  真^真=假 
  或者为: 
  True ^ False = True 
  False ^ True = True 
  False ^ False = False 
  True ^ True = False 
  部分计算机语言用1表示真,用0表示假,所以两个字节按位异或如下 
  00000000 
  异或 
  00000000  
  = 
  00000000 
  ============我是分界线============ 
  11111111 
  异或 
  00000000 
  = 
  11111111 
  =============我还是分界线============= 
  00000000 
  异或 
  11111111 
  = 
  11111111 
  ===========又是我。。。================ 
  11111111 
  异或 
  11111111 
  = 
  00000000 
  =============分界线===================== 
  00001111 
  异或 
  11111111 
  = 
  11110000 
  ======================================== 
  所以 按位异或 也常用于字节取反操作。 
  --------------------------------------------------------------- 
  异或还可以用来交换两个整形变量的值,而不需要第三个量的传递. 
  例如: 
  a=9; 
  b=10; 
  a=a^b; 
  b=b^a; 
  a=a^b; 
  结果是a为10,b为9. 
  4、异或和同或互为非运算。

 

 

 

 

 

 

 

   异或是一种基于二进制的位运算,用符号XOR或者 ^ 表示,其运算法则是对运算符两侧数的每一个二进制位,同值取0,异值取1。它与布尔运算的区别在于,当运算符两侧均为1时,布尔运算的结果为1,异或运算的结果为0。

 

简单理解就是不进位加法,如1+1=0,,0+0=0,1+0=1。

性质

1、交换律

2、结合律(即(a^b)^c == a^(b^c))

3、对于任何数x,都有x^x=0,x^0=x

4、自反性 A XOR B XOR B = A xor  0 = A

异或运算最常见于多项式除法,不过它最重要的性质还是自反性:A XOR B XOR B = A,即对给定的数A,用同样的运算因子(B)作两次异或运算后仍得到A本身。这是一个神奇的性质,利用这个性质,可以获得许多有趣的应用。 例如,所有的程序教科书都会向初学者指出,要交换两个变量的值,必须要引入一个中间变量。但如果使用异或,就可以节约一个变量的存储空间: 设有A,B两个变量,存储的值分别为a,b,则以下三行表达式将互换他们的值 表达式 (值) :

 A=A XOR B (a XOR b)

 B=B XOR A (b XOR a XOR b = a) 

 A=A XOR B (a XOR b XOR a = b)

 类似地,该运算还可以应用在加密,数据传输,校验等等许多领域。

运用距离:

1-1000放在含有1001个元素的数组中,只有唯一的一个元素值重复,其它均只出现
一次。每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空
间,能否设计一个算法实现?

解法一、显然已经有人提出了一个比较精彩的解法,将所有数加起来,减去1+2+...+1000的和。
这个算法已经足够完美了,相信出题者的标准答案也就是这个算法,唯一的问题是,如果数列过大,则可能会导致溢出。
解法二、异或就没有这个问题,并且性能更好。
将所有的数全部异或,得到的结果与1^2^3^...^1000的结果进行异或,得到的结果就是重复数。

但是这个算法虽然很简单,但证明起来并不是一件容易的事情。这与异或运算的几个特性有关系。
首先是异或运算满足交换律、结合律。
所以,1^2^...^n^...^n^...^1000,无论这两个n出现在什么位置,都可以转换成为1^2^...^1000^(n^n)的形式。

其次,对于任何数x,都有x^x=0,x^0=x。
所以1^2^...^n^...^n^...^1000 = 1^2^...^1000^(n^n)= 1^2^...^1000^0 = 1^2^...^1000(即序列中除了n的所有数的异或)。

令,1^2^...^1000(序列中不包含n)的结果为T
则1^2^...^1000(序列中包含n)的结果就是T^n。
T^(T^n)=n。
所以,将所有的数全部异或,得到的结果与1^2^3^...^1000的结果进行异或,得到的结果就是重复数。

当然有人会说,1+2+...+1000的结果有高斯定律可以快速计算,但实际上1^2^...^1000的结果也是有规律的,算法比高斯定律还该简单的多。

1. 布尔逻辑运算符:在C程序中,布尔逻辑运算符(&、|、^、~)用于实现离散数学中的布尔运算,如与、或、异或和取反操作。例如: ``` int a = 2, b = 3; int c = a & b; // 等价于 c = 2 & 3,结果为 2 int d = a | b; // 等价于 d = 2 | 3,结果为 3 int e = a ^ b; // 等价于 e = 2 ^ 3,结果为 1 int f = ~a; // 等价于 f = ~2,结果为 -3 ``` 2. 数组和矩阵:在C程序中,数组和矩阵用于存储和处理离散数学中的序列、组合和排列等概念。例如: ``` int a[5] = {1, 2, 3, 4, 5}; // 定义一个长度为5的一维数组 int b[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; // 定义一个3x3的二维数组 int c[2][3] = {{1, 2, 3}, {4, 5, 6}}; // 定义一个2x3的二维数组 ``` 3. 位运算和移位操作:在C程序中,位运算和移位操作用于对二进制数进行处理和操作,常用于加密算法和数据压缩中。例如: ``` int a = 10, b = 5; int c = a << 1; // 等价于 c = 10 * 2^1,结果为 20 int d = b >> 1; // 等价于 d = 5 / 2^1,结果为 2 int e = a & b; // 等价于 e = 1010 & 0101,结果为 0 int f = a | b; // 等价于 f = 1010 | 0101,结果为 15 ``` 4. 逻辑表达式和条件语句:在C程序中,逻辑表达式和条件语句用于实现离散数学中的命题逻辑和谓词逻辑。例如: ``` int a = 10, b = 5; if (a > b && a < 20) { // 等价于 if (10 > 5 && 10 < 20),结果为真 printf("a is valid"); } else { printf("a is invalid"); } int c = (a > b) ? a : b; // 等价于 if (10 > 5) c = 10; else c = 5;,结果为 10 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值