按位操做符-按位异或(XOR)

上一章咱们认识了按位操做符, 这一章咱们整理一下按位运算的经典案例html

带你认识按位操做符数组

回顾

使用按位操做符的数,会先转成 32 位比特序列,也就是32 位的有符号的整数markdown

若是这个数是正数,若是大于 2 31 1 2^{31}-1 ,只会保留低 32 位, 高于 32 位的数不存储;app

若是这个数是负数,若是小于 2 31 -2^{31} ,只会保留低 32 位, 高于 32 位的数不存储;post

按位异或概述

两个二进制数, 它们对应位的数只有 1 个 1, 结果为 1, 不然, 结果为 0。spa

性质

  1. 自反性: a ^ b ^ b = a
  2. 交换律: a ^ b = b ^ a
  3. 结合律:a ^ (b ^ c) = (a ^ b) ^ c
  4. 恒等律: a ^ 0 = a
  5. 归零律: a ^ a = 0

浮点数转整数

12.12 ^ 0   //12
Math.PI ^ 0  //3
复制代码

JavaScript 默认将数字存储为 64 位浮点数,但按位运算都是以 32位的二进制整数执行。code

两个相同的数按位运算后, 结果只会保留整数部分,小数部分不存储。orm

Infinity ^ 0 //0
Number.MAX_VALUE ^ 0 //0
复制代码

若是一个数大于 2 31 1 2^{31}-1 , 按位运算时只会保留低 32 位运算, 高于 32位的数丢弃, 结果就不许确了。xml

两个相同的数异或为 0

3 ^ 3           //0
12.12 ^ 12.12   //0
复制代码

不用临时变量,交换两个整数的值

let a = 3, b =5;
a = a ^ b;
b = a ^ b;  // 分解开就是 (a ^ b) ^ b, 根据自反性得知,结果为 a,a 赋值给 b
a = a ^ b;  // 分解开就是 a ^ (a ^ b), 根据自反性得知,结果为 b,b 赋值给 a

console.log(a, b)  // 5, 3
复制代码

咱们画图看一下:htm

a = 3 (base 10) = 00000000000000000000000000000011 (base 2)
 b = 5 (base 10) = 00000000000000000000000000000101 (base 2)
                   --------------------------------
 3 ^ 5 (base 10) = 00000000000000000000000000000110 (base 2) = 6 (base 10) = a
 
 //如今 a = 6了
 a = 6 (base 10) = 00000000000000000000000000000110 (base 2)
 b = 5 (base 10) = 00000000000000000000000000000101 (base 2)
                   --------------------------------
 6 ^ 5 (base 10) = 00000000000000000000000000000011 (base 2) = 3 (base 10) = b
 
 //如今 b = 3 了, 已经把 a 的值交换给 b 了
 a = 6 (base 10) = 00000000000000000000000000000110 (base 2)
 b = 3 (base 10) = 00000000000000000000000000000011 (base 2)
                   --------------------------------
 6 ^ 3 (base 10) = 00000000000000000000000000000101 (base 2) = 5 (base 10) = a
复制代码

通过三次 ^操做后, 咱们把 a 和 b的值交换成功了。

找出重复的数

1-1000放在含有1001个元素的数组中,只有惟一的一个元素重复,找出这个重复的数字。

设重复的数为 x, 根据自反性能够获得:
(1 ^ 2 ...... 999 ^ 1000)^ (1 ^ 2 ...... 999 ^ 1000 ^ x) = x
复制代码

找惟一不一样的数

找出惟一一个在数组中出现一次的整数,而其余都会出现两次。

咱们知道两个相同的数异或为 0, 那么把数组中的全部整数异或运算, 剩下的就是惟一出现一次的数。

let arr = [1,2,1,2,3]
let value = arr.reduce((p, c) => p ^ c)
console.log(value)    //3
复制代码

找出不一样的两个数

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

咱们把这一题拆分红上面的那道题就好作了,拆分红两个数组分别异或。

  1. 把两个不一样的数拆分到两个数组中。
  2. 把两个相同的数放到同一个数组中。
const nums = [1, 4, 3, 4]
const x = nums.reduce((p, c) => p ^ c)    //x = a ^ b 
let mark = 1;
//两个不相同的数, 异或对应位的数字至少有 1 位不相同, 咱们找出这个数.
while((x & mark) === 0) mark <<= 1; 
let xor1 = 0;
let xor2 = 0;

for (let n of nums) {
    if(n & mark){   
        xor1 ^= n;
    }else{
        xor2 ^= n;
    }
}

console.log(xor1, xor2) //3,1
复制代码

设 两个不一样的数是 a 和 b

  1. 根据异或自反性可得出,数组全部数异或的结果是 x = a ^ b
  2. a 和 b是不相同的数, a ^ b的结果x确定不为 0,那么至少有 1 位是 1, 咱们找出是哪一位?就能够分组了。
  3. 咱们定义一个 mark=1 ,mark 会左移, 根据 x & mark去找最近一位的 1,若是 x & mark等于 1, 找到位置,不然, mark 继续左移。
  4. 位移操做mark,咱们得知mark中只有 1 位是 1, 其余位都是 0, 因此遍历nums判断n & mark可得出:
  5. 把结果是 1 的放到 xor1 组,不然,放到xor1组。
  6. 相同的数 n & mark的值是同样的, 确定会放在同一个 xor中。

总结

  1. 两个相同的数,异或结果为 0
  2. 使用异或的自反性,能够交换两个数。
  3. 按位与和位移结合使用,能够判断标记位
  4. 用分治法把复杂问题简单化。
相关文章
相关标签/搜索