奇妙的位运算

从二进制说起

世界上有10种人,一种是懂二进制的,另一种是不懂二进制的。

我们习惯性使用的是十进制,0,1,2,3,4,5,6,7,8,9是十进制的10个基数。至于为什么我们使用的是十进制而不是其他的几进制或者几十进制,我想大概是因为人类有10个手指头吧,掰着数到第十个就没得数了,只能进位。类比我们人类思想,在计算机世界中,他们没有手指,只有电流(计算机的CPU、内存等器件都是由二极管三极管构成的),他们都相当于一个开关。只有有电-没电(即真-假)两种状态。所以计算机的物理特性就决定了计算机语言要采用二进制,0和1是二进制的2个基数。

相关术语
  1. ASCII (American Standard Code for Information Interchange):美国信息交换标准代码,它是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准,并等同于国际标准ISO/IEC 646。ASCII第一次以规范标准的类型发表是在1967年,最后一次更新则是在1986年,到目前为止共定义了128个字符。ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符,也就是说我们使用最多8次CPU的通电断电,即可表示我们计算机所需要用到的所有字母、数字、符号等字符。
  2. Bit:比特——表示信息量的最小单位,1个电流的通电断电工作叫做1位,即1Bit
  3. byte:字节——二进制数据的单位。8次通电断电(也就是8bit)即可表示ASCII码表内的任何字符,也就是说我们计算机收到的指令里面的每一个字符都是1byte。1 byte = 8 Bit
  4. kb:1kb = 1024byte,1kb相当于1024个字符,基本上可以满足记事本里的一篇小日记
  5. MB: 1MB = 1024kb,1MB约等于100万个自费,相当于一首中等质量的音乐
  6. GB:1GB = 2024MB,1GB可以存储一部电影

编译系统分配给int型数据2个字节/4个字节(具体由C编译系统决定)如TurboC2.0为每个整数类型分配2个字节(16个二进位),而VisualC++为其分配4个字节(32个二进制位)。若给整型变量分配2个字节,则存储单元中能存放的最大值为0111111111111111,最高位为符号位,表示正数,后面全为1,此数值是(215 - 1),即十进制数32767,最小值为1000000000000000,此数值是(-215),即-32768,因此一个整型变量的值的范围是-32768 ~ 32767,超过此范围则会出现
数值的“溢出”。若分给4个字节,则范围为-231~ 231 - 1,即-2147483648~2147483647。

原码,反码,补码

原码:最高位为符号位,其余各位为数值本身的绝对值
反码:数值为正数时,与原码相同;为负数时,符号位为1,其余位对原码取反(按位取反)
补码:数值为正数时,与原码相同;为负数时,符号位为1,其余位对原码取反再对整个数+1,即反码+1。负数补码的原码=补码的反码+1

🌰 举例:
为了整洁,这里用一个字节,也就是8个比特位表示二进制数。因为最高位为符号位,所以1字节能表示的十进制数的范围是-127-127

十进制原码反码补码
30000 00110000 00110000 0011
-31000 00111111 11001111 1101

计算机数字运算均是基于补码的。

javascript十进制转二进制的方法

注意:该方法只适合十进制的正整数

parseInt(127).toString(2) ---> 1111111
parseInt(43).toString(2) ---> 101011

位运算

常见的位运算符有:与(&)、或(|)、异或(^)、取反(~)、左移(<<)、右移(>>是带符号右移 >>>无符号右移动)

  1. 位与(&):当两位都为1时,结果为1
  2. 位或(|):当两位都为0时,结果为0
  3. 异或(^): 当且仅当两位不同时,结果为1
  4. 取反(~):按位取反,0->1, 1->0
  5. 左移(<<): 高位舍弃,低位补 0
  6. 右移(>>): 低位舍弃,高位正数补 0,负数补1(负数右移操作时,先对负数的原码求其补码再进行右移操作)

在数字没有溢出的前提下,对于正数和负数:
左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方;
右移一位相当于除2向下取整,右移n位相当于除以2的n次方并向下取整。

🌷 举例:

2-2位运算结果原码十进制结果
二进制值0000 00101000 0010
补码0000 00101111 1110
&0000 00100000 00102
\1111 11101000 0010-2
^1111 11001000 0100-4
~1111 11010111 1101

奇技淫巧

  1. 设置x的第n位为1
	x | (1<<n)
  1. 设置x的第n位为0
	x & ~(1<<n)
  1. 将x的第n位取反
	x ^ (1<<n)
  1. 得到最大的int
	int maxInt = ~(1 << 31);
	int maxInt = (1 << 31) - 1;
	int maxInt = (1 << -1) - 1;
  1. 到最小的int
	int minInt = 1 << 31;
	int minInt = 1 << -1;
  1. 计算n乘以2
	n << 1
  1. 计算n除以2
	n >> 1
  1. 计算n乘以2的m次方
	n << m
  1. 就算n除以2的m次方
	n >> m
  1. 判断a和b是否相等
	!(a^b)
  1. 判断n是否为奇数
	(n & 1) == 1
  1. 交换a和b的值
	//方式1
	a ^= b;
	b ^= a;
	a ^= b;
	//方式2
	a = a ^ b ^ (b = a)
  1. 得到x的绝对值
	(x ^ (x >> 31)) - (x >> 31)
  1. 得到a和b中较大的数
	b & ((a-b) >> 31) | a & (~(a-b) >> 31)
  1. 得到a和b中较小的数
	a & ((a-b) >> 31) | b & (~(a-b) >> 31)
  1. 判断a和b是否同正负
	(x ^ y) >= 0
  1. 计算i的相反数
	//方式1
	i = ~i + 1
	//方式2
	i = (i ^ -1) + 1
  1. 判断n是否为2的若干次方
	n > 0 && (n & (n - 1)) == 0
  1. 拿到x的位于最右的1位
	x & (-x)
  1. 拿到x的位于最右的0位
	~x & (x+1)
  1. 将x的位于最右的0位设置为1
	x | (x+1)
  1. 计算n + 1
	-~n
  1. 计算n - 1
	~-n
  1. 将x(x为字符)转换为小写(如果原来就已经为小写则保持)
	x | ' '
  1. 将x(x为字符)转换为大写(如果原来就已经为大写则保持)
	x & '_'
  1. 大小写互换(x为字符)
	x ^ ' '
  1. 得到x(x为字符)在字母表中的位置(大小写都适用)
	x & '\x1F'

相关算法题解

1.【2的幂】

给定一个整数n,如果它是2的幂,则返回true,否则返回false ——【leetCode 231】

题目分析:如果是2的幂,则这个数一定大于等于0,并且转化为二进制数必然为1后面加若干个0。假设为1000,那么1000 - 1 = 0001。那么这个整数n满足:
n & (n-1) = 0;

题解:

/**
 * @param {number} n
 * @return {boolean}
 */
var isPowerOfTwo = function(n) {
    return (n > 0 && (n & (n-1)) === 0)
};

2.【4的幂】

给定一个整数,写一个函数来判断它是否是 4 的幂次方。如果是,返回 true ;否则,返回 false 。 ——【leetCode 342】

题目分析:4x = 22x, 4的幂一定是2的幂,但是2的幂不一定是4的。
推理可得:
22x % 3 = 1;
22x+1 % 3 = 2;

题解:

/**
 * @param {number} n
 * @return {boolean}
 */
var isPowerOfFour = function(n) {
    return n > 0 && (n & (n-1)) === 0 && n % 3 === 1
};

3.【位1的个数】

编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。——【力扣191】

题目分析:二进制n 不断的位与n-1,最后得到的一定是0,那么位与的次数就是1的个数

题解:

/**
 * @param {number} n - a positive integer
 * @return {number}
 */
var hammingWeight = function(n) {
    let count = 0
    while(n) {
        n &= n-1
        count++
    }
    return count
};

4.【交换变量】

编写一个函数,不使用临时变量,交换两个变量

题目分析:任何数异或它本身等于0,异或0等于它本身

题解:

var swapNumbers = function(arr) {
    arr[0] = arr[0] ^ arr[1]
    arr[1] = arr[0] ^ arr[1]
    arr[0] = arr[0] ^ arr[1]
    return arr
};

5.【只出现一次的数字】

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?——【力扣136】

题目分析:异或的性质,a ^ a = 0; a ^ 0 = a; 满足交换律和结合律: a ^ b = b ^ a; (a ^ b) ^ c = a ^ (b ^ c);

题解:

/**
 * @param {number[]} nums
 * @return {number}
 */
var singleNumber = function(nums) {
    return nums.reduce((res, item) => res^item)
    // let res = 0
    // for (i = 0; i < nums.length; i++) {
    //     res ^= nums[i]
    // }
    // return res
};

6.【汉明距离】

两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。给你两个整数 x 和 y,计算并返回它们之间的汉明距离。——【力扣461】

题目分析:不同位异或为1,则此题转化为求两数字异或后1出现的次数

题解:

/**
 * @param {number} x
 * @param {number} y
 * @return {number}
 */
var hammingDistance = function(x, y) {
    let z = x ^ y
    let count = 0
    while (z) {
        z &= (z - 1)
        count++
    }
    return count
};

7.【交替位二进制数】

给定一个正整数,检查它的二进制表示是否总是 0、1 交替出现:换句话说,就是二进制表示中相邻两位的数字永不相同。——【力扣693】

题目分析:01交替出现,01 & 11 = 01; 11&11=11; 00&11 = 00;所以只要将相邻两位不断位与11——即十进制数字3,结果出现3或者0则表示不是交替位二进制数,否则是。

题解:

/**
 * @param {number} n
 * @return {boolean}
 */
var hasAlternatingBits = function(n) {
    while (n) {
        if ((n & 3) === 0 || (n & 3) === 3) {
            return false
        }
        n >>= 1
    }
    return true
}

8.【两整数之和】

给你两个整数 a 和 b ,不使用 运算符 + 和 - ​​​​​​​,计算并返回两整数之和。——【力扣371】

题目分析:异或操作符:两个位不相同时为1,否则为0.这个操作符也可以理解为不带进位的加法。位与操作符,都为1时才为1,否则为0,因此&可以理解为只含进位加法。a + b = (a ^ b) + ((a & b) << 1);a ^ b是无进位的相加; a&b得到每一位的进位;让无进位相加的结果与进位不断的异或, 直到进位为0;
题解:

/**
 * @param {number} a
 * @param {number} b
 * @return {number}
 */
var getSum = function(a, b) {
    while (b != 0) {
        const carry = (a & b) << 1
        a = a ^ b
        b = carry
    }
    return a
	// return b === 0 ? a : getSum(a ^ b, (a&b)<<1)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值