小时候不懂事,不知道算法的重要性,在此明志苦练算法
位运算:
例1:
将int转化成二进制 常规思路: 再算二进制转十进制时,我们将二进制上每一位乘以其权重再相加,反过来计算的思路就是:用十进制数尝试投影到二进制的高位,投影成功后再将十进制数剩下的部分投影到次高位。
代码如下:
public void toBin2(int target){
//最常规思路:由二进制转十进制的思路反转——将数投影在二进制的各个位置
for (int i = 31; i >= 0 ; i--) {
int curr = (int) ( target / Math.pow(2, i));
System.out.print(curr);
target = (int) (target % Math.pow(2,i));
}
}
思路给人的感觉就像这个:
一层层滤出高位的值。
思路2: 内含位移运算的思想: 当我们将数右移一位的时候,是将其值/2,如果有余数表示原本最右边的值为1,没有余数则表示最右边的值为0。 所以我们每次将十进制数num除以2,如果有余数则记录下为1,没有则记录为0 代码:
public void toBin(int target) {
//思路是:用target除以2,余数表示此位的数,再循环用商除以2。
if(target <= 1){
System.out.println(target);
return;
}
System.out.print(target % 2);
toBin(target / 2);
}
思路3: 纯粹的位运算思想: 十进制数对应的二进制数,通过某些位运算可以暴露出某个位上是1还是0 例如a = 0010101,将 a & 0000001 是否等于0,可以判断a的最右边一位是否为0 代码:
public void toBin3(int target) {
for (int i = 31; i >= 0; i--) {
System.out.print((target & (1 << i)) == 0 ? 0 : 1);
}
}
例2:
熟练使用异或
基于异或最直观的性质:相同为0,不同为1,有几条更加便于使用的性质:
- 0 ^ N = N
- N ^ N = 0
- 可以将异或操作理解为:无进位相加
- 异或操作具有交换律
- 异或操作具有结合律
怎么理解以上操作?
无进位相加是指:二进制的各个位置上依次相加,但不进行进位。
按照无进位相加进行理解,进行异或其实可以看作各个位置上对有多少个1进行计数,有奇数个1则此为为1,反之成立。
这样就可以理解交换律结合律,以及0异或N等于N,N异或N等于0;
例1:
一个int数组中,只有一个数出现了奇数次,其他都出现偶数次,找出出现奇数次的数 {1,3,2,1,1,41,41,2,244,3,244}
常规思维: 遍历一遍,每个数和其出现次数加入哈希表,最后再遍历一遍哈希表 时间复杂度为O(N),空间复杂度也是O(N),但是用位运算可以将空间复杂度降低
位运算思路: 如果我将所有数全都异或一遍,出现偶数次的数会抵消,出现奇数次的数会保留 代码:
public int getSingle2(int[] ints) {
int eor = 0;
for (int num : ints
) {
eor = eor ^ num;
}
return eor;
}
说实话,优雅是优雅,但是很难将次方法进行拓展哎,只能怪咱功夫还不到家
例2:
一个int数组中,只有两个数出现了奇数次,其他都出现偶数次,找出出现奇数次的数 {1,3,2,1,1,41,41,2,3,244,3,244}
假如还要从位运算的角度突破,想想我们能得到什么 假设出现奇数次的数是a,b 依照异或运算的性质将所有数异或一遍,自然可以得到a ^ b 之后就是左神的神奇思路: a和b既然是不同的数,那么a和b肯定存在某一位不同,按照此位将数组中所有数分为两份,一份是此位是0,一份是此位是1(a,b自然分置在两份中),很容易想到两两成对的数都在一份中。 用a ^ b去异或其中一份中所有数,异或其中出现两次的数没有变化,而异或其中的a或者b会将a^b 消掉一个,得到a或者b 代码:
public int[] getSingle3(int[] ints) {
int eor = 0;
for (int num : ints
) {
eor = eor ^ num;
}
int ab = eor; //a ^ b
int i = 0; //用最右侧的1位上,这些数是0还是1来区分所有数
for (; i < 32; i++) { //这一步是得到ab最右侧的1所在位置
if ((ab ^ (int)(Math.pow(2, i))) < ab) {
break;
}
}
int ans = (int) Math.pow(2,i);
int temp = 0;
for (int num : ints
) {
if ((num ^ ans) < num) { //判断在这一位上是不是1,如果是1,异或1后会减少
temp = temp ^ num;
}
}
eor = eor ^ temp;
int another = eor ^ ab;
return new int[]{another, eor};
}
- 另外得到一个数最右边的1的办法还有:
原理不明,优就完事了int index = eor & (~eor+1) - 另外怎么判断某个位置上是0还是1呢?这在位运算中有很多办法 假设要根据最后一位区分01001和01000 1. 用00001位与,如果结果是0,则表示原数最后一位是0 2. 用11110位或,如果结果比11110大,则表示原数最后一位是1 3. 用00001异或,其他位置不变,最后一位原本是1变为0,原本是0变为1,所以如果数和原本相比减少,则最后一位是1
待续......