背景
今天做每日一题的时候,发现这个每日一题之前好像做过,看看题号还有提交记录,感觉应该是之前某场周赛做过的题目 话说好久没做周赛了 23333
结果自信一跑,发现测试样例一直有一个没通过,开始怀疑算法和题目,最后的最后发现,忽略了题目中的条件
目标数组首先长度是偶数
哦,那鲁ho多,于是我果断把数组长度 * 2 再 /2,一提交,果然A了
然后我又想,这样乘二除二是在是不够优雅,有无直接把最后一位给抹掉的方式,最好也不要加if语句去判断
然后我隐隐约约想到了go的一个很冷的运算符,&^,没错,是由与运算符和异或运算符拼一起的一个出场率极低的运算符,它的作用就是按位置0
7 &^ 1 = 6 // 111 &^ 001 = 110
7 &^ 2 = 5 // 111 &^ 010 = 101
7 &^ 4 = 3 // 111 &^ 100 = 011
即a &^ b = c
,如果b中对应位是1,则将a中对应位给置为0
那么我又好奇,是不是单独依靠与,或,异或无法一次做到上述的需求?试了半天,好像确实不行
那话又说回来,按位置零这个操作是咋实现的呢,用到&^是不是就是靠这两种操作顺序进行得到的,于是我试了一下
package main
import "fmt"
func main() {
a := 7
b := 4
fmt.Println(a &^ b) // 3
fmt.Println((a ^ b) & a) // 3
}
看来确实如我所设想的一样,a和b异或的结果就是b位为1在a中的对应位,如果是1则置0,如果为0则变为1,后再和a进行与,保证原来为0的位还是0即可
场景
那既然说到了位运算,那就简单总结一下之前遇到过的常见的位运算的操作
一般涉及到位,要么是二进制的数学运算,用么就是借由位进行统计
像bitmap也是用来用位进行统计
左移右移
- 1. 快速乘2或者除2
异或
- 1. 交换两个数
- 2. 支持交换律与结合律
常见题目
1. 统计字符串中的字符是不是都只出现过一次
这是秋招时候华为考到的一道题
我的思路主要是考虑字符串中字符的类型和范围,如果是覆盖整个ASCII码(0-127),那用两个uint64整型的所有位进行统计,最后再看位1的个数是否和字符串的长度一致
优化思路:哈希表 -> 切片 -> 数组 -> 变量+位运算
2. 不使用算术运算符'+'实现加法操作
这是当时在某个刷题网站上看到的第一题,当时看题目以为是真正的两数之和,结果看到进阶要求,整个人虎躯一震,竟然还有这种操作
后面自己尝试着做了出来,发现脑海中当年学计组时候,ALU那章节的记忆隐隐约约浮现了出来
3. 位1个数
就是通过位运算快速得到某个数字,它在二进制下有多少个位是1
好像和lowBit算法(用于获取一个数从右往左第一个位为1对应的值)有关,不过一般循环用x & (x - 1)直到x为0,记录循环次数
4. 只出现一次的数字
秋招被考到过的一题,也算是热门题了,即在一个数组中,除了某个数字,其它的数字都出现了两次,请找出这个数字
扩展的话,就是数组中有个数字只出现了1次,其它数字都出现了三次
主要是用到了异或的性质
5. 快速幂
和数位的权重相关