Hello world!很久没写博客了。家庭与生活,工作与力扣,难舍难分,总之一言难尽。今天写一下我对二进制的一知半解,分享给各位,共勉!
说起二进制,我们不得不从 0 开始。假设现在让我们数数,一般我们数到 9 的下一个数就会进十,变成 10。而二进制,就是数到 2 变成 10。任何进制包括 8 或 16 进制的进制原理都和这个类似。
我们用计算机存储的数据,都会使用二进制进行存储,也就是 n 个 0 与 n 个 1 进行组合。主要因为二进制的数制系统相较于其他进制只有 0 和 1 两个标识,简单且方便计算机识别与处理。
下面简单介绍这些运算符。
按位与的运算原理,若把 1 看作 true 的话与逻辑运算符 && 很像。当两个数的二进制同位都为 1 时,运算为 1,否则为 0。
(1 & 1) = (01 & 01) = 01
(1 & 0) = (01 & 00) = 00
(2 & 1) = (10 & 01) = 00
按位或与逻辑运算的 || 很像。当两个数的二进制同位只要存在1个或2个 1 时,运算都为 1,否则为 0。
(1 | 1) = (01 | 01) = 01
(1 | 0) = (01 | 00) = 01
(2 | 1) = (10 | 01) = 11
按位异或就是当两个数的二进制同位进行差值取绝对值。
(1 ^ 1) = (01 ^ 01) = 00
(1 ^ 0) = (01 ^ 00) = 01
(2 ^ 1) = (10 ^ 01) = 11
取反,顾名思义,就是将二进制每位上的 0 变为 1,1 变为 0。
(~2) = (~10) = 11111111111111111111111111111101
(~3) = (~11) = 11111111111111111111111111111100
左移,顾名思义,就是将二进制中右侧的第一个 1 到左侧的部分向左移动几个单元,也可以说是在二进制后补几个 0
(1 << 1) = (01 << 1) = 10
(9 << 3) = (1001 << 3) = 1001000
(2 << 3) = (10 << 3) = 10000
右移,与左移相反,右移若没有位数可以移动,就是 0
(1 >> 1) = (01 >> 1) = 00
(9 >> 3) = (1001 >> 3) = 01
(2 >> 3) = (10 >> 3) = 00
为了更充分的利用刚刚学到的二进制运算符原理,下面我们直接进入今天的主题,用二进制都能干啥?
二进制如果利用得当,可以很大程度的提高程序的运行效率。
对于阿斯克码值中,数字范围与种类相对较小的就是英文的大小写字符。大小写分别为 26 种,也就是 A ~ Z 与 a ~ z。那么对于一个字符串来说,如果纪录这串字符中都使用了哪些字母,我们可以使用二进制来解决。
字符串:abcdefg - 0123456 - 1111111 - 127
解释:对于小写字符,a 可以看为 0,z 可以看作 25,最高不超过 int 类型的二进制最大位的31位。因此,这里不考虑原字符串的字母排列顺序以及每个字母的使用频次,那么使用 int 来存储所有使用过的字符完全足够。也就是说,127 这个数字告诉我们,它的二进制 0 到 6 位都是 1,那么通过阿斯克码转换,可以知道这个字符串使用了 a ~ g 这 7 个字符
代码:
public int charToNum(String str) {
int mask = 0;
for (int c : str.toCharArray()) {
mask |= (1 << c - 'a');
}
return mask;
}
基于前面的字符串转数字的技巧,我们可以更进一步的使用这种方式,来比较两个英文字符串是否存在相同字符。
字符串:hello vs world
hello - 00000000100100010010000 - 18576
world - 10000100100100000001000 - 4343816
解释:直观可以看到有 2 处的 1 处在同位,意味着存在 2 种冲突的字符
代码:
public boolean conflict(String s1, String s2) {
int a = charToNum(s1), b = charToNum(s2);
return (a & b) != 0;
}
这里我们使用按位与(&)可以巧妙的解决两个数字的相同二进制位是否存在都为 1 的情况。
知道了是否冲突,如果更进一步需要知道存在几种冲突,我们可以考虑枚举二进制串中存在几个 1 即可。这里需要思考:使用哪些二进制运算符,以及如果一次性无法计算出某些结果,我们该如何组合使用二进制运算符,并设计一个程序来帮我们求得结果。单个二进制运算符的运算能力有限,因此当我们对二进制运算符掌握的足够牢靠,以及有相当够的经验,很多新的问题也可以尝试去使用二进制运算符解决。就好比当我们只知道加法时,对于 6 个 6 相加,我们需要一个一个的累加求得结果,而不是 6 乘以 6。
代码:
public int countConflicts(String s1, String s2) {
int binary = charToNum(s1) & charToNum(s2);
int mask = 1, cnt = 0;
// 31 是二进制 int 最大长度
for (int i = 0; i < 31; ++i) {
if ((binary & mask) != 0) { ++cnt; }
mask <<= 1;
}
return cnt;
}
当然,int 类型的二进制位最大支持 31 位,更大的可以选择 long 类型的 63 位了。对于字符串转数字,只要计算的维度的取值范围不超过 63,我们都可以采取二进制的方式去进行程序设计。汉字不像英文字符仅有 26 个,但汉字的拼音确是由 26 个英文字母组成,如果你想象力足够丰富,是可以设计出意想不到的程序的。