技术学习记录
文章
算法

按位的操作

内容主要来自 https://www.bilibili.com/video/BV13g41157hK?spm_id_from=333.788.videopod.episodes&p=3

JavaScript 中按位的操作(与、或、异或、按位取反、左移、右移)

按位与 & :

二进制都为1时为1,出现0为0

js
const a = 2 // 10
const b = 3 // 11
const c = a & b // 10
// c = 2

按位或 | :

二进制都为0时为0,出现1为1

js
const a = 6 // 110
const b = 5 // 101
const c = a | b // 111
// c = 7

异或 ^ :

相同为0,不同为1

js
const a = 2 // 010
const b = 4 // 100
const c = a ^ b // 110
// c = 6

取反 ~ :

JS按位取反运算符~,是对一个表达式执行位非(求非)运算。如~1 = -2,~-3=2,~true=-2,~false=-1

按位取反的运算规则步骤:

1、十进制转成原码

转成二进制原码,最高位是符号位,0为正数,1为负数

js
    十进制   ---->  原码
      1   ---->  00000001
     -1   ---->  10000001

2、原码转成反码

正数的反码就是原码,负数的反码是符号位不变,其余位取反

js
    十进制  ---->   原码    ---->  反码
      1    ----> 00000001 ----> 00000001
     -1    ----> 10000001 ----> 11111110

3、反码转成补码

正数的补码还是原码,负数的补码是在反码的基础上加1

js
    十进制  ---->    原码   ---->   反码   ---->  补码
      1    ----> 00000001 ----> 00000001 ----> 00000001
     -1    ----> 10000001 ----> 11111110 ----> 11111111

4、补码取反得原码

正整数补码取反之后符号位置为1,是一个负整数,所以再按照负整数计算补码的方式逆运算得到原码

逆运算得到原码,首先将取反的补码转成反码,公式:反码=补码 - 1,然后将反码转成原码,符号位不变,其他位取反

js
  十进制  ---->    原码    ---->   反码    ---->   补码     ---->  补码取反   ---->  取反补码转成反码  ---->  转成原码
    1   ----> 00000001  ----> 0000001  ---->  00000001  ----> 11111110   ---->   11111101    ---->  10000010

负整数补码取反之后符号位置为0,是一个正整数,因正整数的反码与补码就是本身,所以不需要再进行逆运算

js
  十进制  ---->    原码    ---->   反码     ---->   补码      ---->   补码取反得原码
    -1  ---->  10000001  ----> 11111110  ---->  11111111   ---->     00000000

5、将原码转成二进制

js
 十进制  ---->    原码    ---->   反码    ---->   补码     ---->  补码取反   ---->  取反补码转成反码  ---->  转成原码  ---->  转成二进制
    1   ----> 00000001  ----> 0000001  ---->  00000001  ----> 11111110   ---->   11111101    ---->  10000010 ---->   -2

  十进制  ---->    原码    ---->   反码     ---->   补码      ---->   补码取反得原码   ---->  转成二进制
    -1  ---->  10000001  ----> 11111110  ---->  11111111   ---->    00000000     ---->     0

所以,~1=-2,~-1=0

左移 <<:

js
2 << 1 = 4
// 10 -> 100 = 4

右移 >> :

js
2 >> 1 = 1
// 10 -> 01 = 1

异或的算法题(1):交换两个变量的值

性质:

  1. 满足交换律和结合律,即 a ^ b = b ^ a; a^ b ^c = a ^ (b^c)
  2. a^0 = a; a^a=0

交换两个变量的值一般方法需要新变量,即:

js
function swap(a, b) {
  const temp = a
  a = b
  b = temp
}

异或操作实现:

js
function swap(a, b) {
  a = a ^ b
  b = a ^ b
  a = a ^ b
}

解释:

a = a ^b;

b = a^b = (a^b)^b = a^ (b^b) = a^0 = a;

a = a^b = (a^b)^a = (a^a)^ b = 0^b = b;

异或的算法题(2): 长度为n的数组中有一个数a出现奇数次,其他数都出现偶数次,找到数a,要求时间复杂度0(n),空间复杂度O(1)

js
const list = [1, 23, 23, 3, 3, 4, 4, 4, 4, 56, 56, 23, 23, 8, 8, 8, 8, 9, 9, 9, 1]
function getNum(list) {
  let are = 0
  list.forEach((item) => {
    are = are ^ item
  })
  return are
}
console.log('getNum(list)', getNum(list)) // 9

异或的算法题(3): 长度为n的数组中有两个数a,b出现奇数次,其他数都出现偶数次,找到数a,b,要求时间复杂度0(n),空间复杂度O(1)

js
const list = [1, 23, 23, 3, 3, 4, 4, 4, 4, 4, 56, 56, 23, 23, 8, 8, 8, 8, 9, 9, 9, 1]
function getNum(list) {
  let are = 0
  list.forEach((item) => {
    are = are ^ item
  })
  const rightOne = are & (~are + 1)
  let onlyOne = 0
  list.forEach((ietm) => {
    if (ietm & rightOne == 1) {
      onlyOne = onlyOne ^ ietm
    }
  })
  return [onlyOne, are ^ onlyOne]
}
console.log('getNum(list)', getNum(list)) // [ 9, 4 ]

解释:首先

js
let are = 0
list.forEach((item) => {
  are = are ^ item
})

之后 are = a^b,且 are!== 0; 需要将a,b分开;

a与b不相等,则a与b的原码至少一位不相同,are至少有一位为1,必然存在第x位a为1而b为0,根据第x位将数组分为两部分,onlyOne 异或每一部分,则能提取出a或b,即a^are = b, b^are = a; 即are^(a或b)=(b或者a);

a & rightOne == 1 就是用来区分为两部分的方法,具体:

const rightOne = are & (~are + 1); 提取出 are 最右侧的1;

eg:

js
             are = 1001010;
            ~are = 0110101;
		  ~are+1 = 0110110;
are & (~are + 1) = 0000010

参考文章:

https://www.cnblogs.com/minorf/p/13225505.html https://www.cnblogs.com/minorf/p/13225505.html

https://blog.csdn.net/romeo12334/article/details/81234991 https://blog.csdn.net/romeo12334/article/details/81234991

喜欢 0
评论区在赶来的路上...