内容主要来自 https://www.bilibili.com/video/BV13g41157hK?spm_id_from=333.788.videopod.episodes&p=3
JavaScript 中按位的操作(与、或、异或、按位取反、左移、右移)
按位与 & :
二进制都为1时为1,出现0为0
const a = 2 // 10
const b = 3 // 11
const c = a & b // 10
// c = 2
按位或 | :
二进制都为0时为0,出现1为1
const a = 6 // 110
const b = 5 // 101
const c = a | b // 111
// c = 7
异或 ^ :
相同为0,不同为1
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为负数
十进制 ----> 原码
1 ----> 00000001
-1 ----> 10000001
2、原码转成反码
正数的反码就是原码,负数的反码是符号位不变,其余位取反
十进制 ----> 原码 ----> 反码
1 ----> 00000001 ----> 00000001
-1 ----> 10000001 ----> 11111110
3、反码转成补码
正数的补码还是原码,负数的补码是在反码的基础上加1
十进制 ----> 原码 ----> 反码 ----> 补码
1 ----> 00000001 ----> 00000001 ----> 00000001
-1 ----> 10000001 ----> 11111110 ----> 11111111
4、补码取反得原码
正整数补码取反之后符号位置为1,是一个负整数,所以再按照负整数计算补码的方式逆运算得到原码
逆运算得到原码,首先将取反的补码转成反码,公式:反码=补码 - 1,然后将反码转成原码,符号位不变,其他位取反
十进制 ----> 原码 ----> 反码 ----> 补码 ----> 补码取反 ----> 取反补码转成反码 ----> 转成原码
1 ----> 00000001 ----> 0000001 ----> 00000001 ----> 11111110 ----> 11111101 ----> 10000010
负整数补码取反之后符号位置为0,是一个正整数,因正整数的反码与补码就是本身,所以不需要再进行逆运算
十进制 ----> 原码 ----> 反码 ----> 补码 ----> 补码取反得原码
-1 ----> 10000001 ----> 11111110 ----> 11111111 ----> 00000000
5、将原码转成二进制
十进制 ----> 原码 ----> 反码 ----> 补码 ----> 补码取反 ----> 取反补码转成反码 ----> 转成原码 ----> 转成二进制
1 ----> 00000001 ----> 0000001 ----> 00000001 ----> 11111110 ----> 11111101 ----> 10000010 ----> -2
十进制 ----> 原码 ----> 反码 ----> 补码 ----> 补码取反得原码 ----> 转成二进制
-1 ----> 10000001 ----> 11111110 ----> 11111111 ----> 00000000 ----> 0
所以,~1=-2,~-1=0
左移 <<:
2 << 1 = 4
// 10 -> 100 = 4
右移 >> :
2 >> 1 = 1
// 10 -> 01 = 1
异或的算法题(1):交换两个变量的值
性质:
- 满足交换律和结合律,即 a ^ b = b ^ a; a^ b ^c = a ^ (b^c)
- a^0 = a; a^a=0
交换两个变量的值一般方法需要新变量,即:
function swap(a, b) {
const temp = a
a = b
b = temp
}
异或操作实现:
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)
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)
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 ]
解释:首先
jslet 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:
jsare = 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