从二进制说起
世界上有10种人,一种是懂二进制的,另一种是不懂二进制的。
我们习惯性使用的是十进制,0,1,2,3,4,5,6,7,8,9是十进制的10个基数。至于为什么我们使用的是十进制而不是其他的几进制或者几十进制,我想大概是因为人类有10个手指头吧,掰着数到第十个就没得数了,只能进位。类比我们人类思想,在计算机世界中,他们没有手指,只有电流(计算机的CPU、内存等器件都是由二极管三极管构成的),他们都相当于一个开关。只有有电-没电(即真-假)两种状态。所以计算机的物理特性就决定了计算机语言要采用二进制,0和1是二进制的2个基数。
相关术语
- ASCII (American Standard Code for Information Interchange):美国信息交换标准代码,它是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准,并等同于国际标准ISO/IEC 646。ASCII第一次以规范标准的类型发表是在1967年,最后一次更新则是在1986年,到目前为止共定义了128个字符。ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符,也就是说我们使用最多8次CPU的通电断电,即可表示我们计算机所需要用到的所有字母、数字、符号等字符。
- Bit:比特——表示信息量的最小单位,1个电流的通电断电工作叫做1位,即1Bit
- byte:字节——二进制数据的单位。8次通电断电(也就是8bit)即可表示ASCII码表内的任何字符,也就是说我们计算机收到的指令里面的每一个字符都是1byte。1 byte = 8 Bit
- kb:1kb = 1024byte,1kb相当于1024个字符,基本上可以满足记事本里的一篇小日记
- MB: 1MB = 1024kb,1MB约等于100万个自费,相当于一首中等质量的音乐
- 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
十进制 | 原码 | 反码 | 补码 |
---|---|---|---|
3 | 0000 0011 | 0000 0011 | 0000 0011 |
-3 | 1000 0011 | 1111 1100 | 1111 1101 |
计算机数字运算均是基于补码的。
javascript十进制转二进制的方法
注意:该方法只适合十进制的正整数
parseInt(127).toString(2) ---> 1111111
parseInt(43).toString(2) ---> 101011
位运算
常见的位运算符有:与(&)、或(|)、异或(^)、取反(~)、左移(<<)、右移(>>是带符号右移 >>>无符号右移动)
- 位与(&):当两位都为1时,结果为1
- 位或(|):当两位都为0时,结果为0
- 异或(^): 当且仅当两位不同时,结果为1
- 取反(~):按位取反,0->1, 1->0
- 左移(<<): 高位舍弃,低位补 0
- 右移(>>): 低位舍弃,高位正数补 0,负数补1(负数右移操作时,先对负数的原码求其补码再进行右移操作)
在数字没有溢出的前提下,对于正数和负数:
左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方;
右移一位相当于除2向下取整,右移n位相当于除以2的n次方并向下取整。
🌷 举例:
2 | -2 | 位运算结果 | 原码 | 十进制结果 | |
---|---|---|---|---|---|
二进制值 | 0000 0010 | 1000 0010 | |||
补码 | 0000 0010 | 1111 1110 | |||
& | 0000 0010 | 0000 0010 | 2 | ||
\ | 1111 1110 | 1000 0010 | -2 | ||
^ | 1111 1100 | 1000 0100 | -4 | ||
~ | 1111 1101 | 0111 1101 |
奇技淫巧
- 设置x的第n位为1
x | (1<<n)
- 设置x的第n位为0
x & ~(1<<n)
- 将x的第n位取反
x ^ (1<<n)
- 得到最大的int
int maxInt = ~(1 << 31);
int maxInt = (1 << 31) - 1;
int maxInt = (1 << -1) - 1;
- 到最小的int
int minInt = 1 << 31;
int minInt = 1 << -1;
- 计算n乘以2
n << 1
- 计算n除以2
n >> 1
- 计算n乘以2的m次方
n << m
- 就算n除以2的m次方
n >> m
- 判断a和b是否相等
!(a^b)
- 判断n是否为奇数
(n & 1) == 1
- 交换a和b的值
//方式1
a ^= b;
b ^= a;
a ^= b;
//方式2
a = a ^ b ^ (b = a)
- 得到x的绝对值
(x ^ (x >> 31)) - (x >> 31)
- 得到a和b中较大的数
b & ((a-b) >> 31) | a & (~(a-b) >> 31)
- 得到a和b中较小的数
a & ((a-b) >> 31) | b & (~(a-b) >> 31)
- 判断a和b是否同正负
(x ^ y) >= 0
- 计算i的相反数
//方式1
i = ~i + 1
//方式2
i = (i ^ -1) + 1
- 判断n是否为2的若干次方
n > 0 && (n & (n - 1)) == 0
- 拿到x的位于最右的1位
x & (-x)
- 拿到x的位于最右的0位
~x & (x+1)
- 将x的位于最右的0位设置为1
x | (x+1)
- 计算n + 1
-~n
- 计算n - 1
~-n
- 将x(x为字符)转换为小写(如果原来就已经为小写则保持)
x | ' '
- 将x(x为字符)转换为大写(如果原来就已经为大写则保持)
x & '_'
- 大小写互换(x为字符)
x ^ ' '
- 得到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)
}