下面主要介绍利用二进制的位运算解决问题,二进制和其他进制之间的一些以及二进制间的一些规律和技巧运用。
一、二进制中1的个数
问题如下:输入一个整数,输出该数二进制表示中1的个数
例如:9的二进制是1001, 有两个1。
解法一:最简单的方法是用一个1进行不断的移位,然后和输入的数字进行与运算,如果结果为0就表示该数的二进制表示在这个位置上为0.否则为1(所以得的结果和1移位后的数相等).
例如1001和1进行与运算结果为1,证明第一位为1(从右向左数)。将1左移2位得到10然后和1001进行与运算得到0,所以第二位为0. 因为一个整数是32位,所以只需要循环32次。代码如下:
package 位运算的奇巧淫技;
import java.util.*;
public class 二进制中1的个数解法一 {
public static void main(String [] args){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
//将n的二进制字符串表示给打印出来
System.out.println(Integer.toString(n, 2));
int count = 0;
for(int i=0; i<32; i++){
if((n&(1<<i))!=0) count++;
}
System.out.println(count);
}
}
解法二:和解法一类似。1不动,将输入的整数进行右移然后和1进行与运算。代码省略,将上面解法一if语句修改为如下即可:
if(((n>>i)&1)!=0) count++;
解法三:这个方法用到二进制运算的一个规律:(x-1)& x 会消去x最低位的那一个1. 也就是说X减一和X进行与运算会消去X最低位的那一个1.如下图。利用这个规律我们就可以轻松的解决这个问题,也就是说如果一个数的二进制表示有三个1,就需要进行三次运算得到0.我们只需要统计做了几次这样的运算就可以得出这个二进制有几个1.
代码如下:
package 位运算的奇巧淫技;
import java.util.*;
public class 二进制中1的个数解法三 {
public static void main(String [] args){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
System.out.println(Integer.toString(n, 2));
int count = 0;
while(n!=0){
n = (n-1)&n;
count++;
}
System.out.println(count);
}
}
二、用一条语句判断一个整数是不是2的整数次方
解题思路:这题用到一个规律:一个整数是2的整数次方,那么它的二进制表示中就只有一个1.注意:1是2的零次方。也就是将问题转换为判断一个整数的二进制表示是否只有一个1;而且只用一条语句。对于这个问题我们用上题的规律可以轻松的解决:(x-1)& x 会消去x最低位的那一个1。进行这个运算后如果结果为零,就表示这个二进制只有一个1.
代码如下:
package 位运算的奇巧淫技;
import java.util.Scanner;
public class 判断整数是不是2的整数次方 {
public static void main(String [] args){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
System.out.println(Integer.toString(n, 2));
if(((n-1)&n)==0)
System.out.println("是2的整数次方");
else
System.out.println("不是2的整数次方");
}
}
三、将二进制整数的奇偶位互换
例如:1001的奇偶位进行互换得到0110.
解题思路:分别保留偶数位和奇数位得到C和D,然后将C左移一位,将D右移一位;然后将C和D进行异或运算就可以得到奇偶位互换后的结果。
保留偶数位的方法如下:与偶数位为1奇数位为0的二进制进行与运算
保留奇数位的方法如下:与奇数位为1偶数位为0的二进制进行与运算
代码如下:
package 位运算的奇巧淫技;
import java.util.Scanner;
public class 奇偶位互换 {
public static void main(String [] args){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
System.out.println(Integer.toString(n, 2));
int ou = n&0xaaaaaaaa;
//用十六进制表示,a表示1010,因为整数是32位所以要8个a
int ji = n&0x55555555;
//5表示0101
System.out.println((ji<<1)^(ou>>1));
System.out.println(Integer.toString((ji<<1)^(ou>>1), 2));
}
}
四、数组中只有一个数出现了一次,其他的数都出现了k次(k大于1),k是一个常数,请输出只出现了一次的数。
假设本题k等于3,即其他数都出现了三次。
解题思路:暴力法就不说了,这题用位运算解决要用到一个规律: N个相同的N进制进行不进位的加法结果为0.比如:两个相同的二进制进行不进位的加法(相当于异或运算)结果为0. 有了这个规律我们可以将数组的数都转换为是三进制,然后进行相加。最终结果就会得到那个只出现一次的数的三进制表示。把这个三进制转换为十进制就行了。
代码如下:
package 位运算的奇巧淫技;
import java.util.Arrays;
import java.util.Scanner;
public class 只出现了一次的数 {
public static void main(String [] args){
int[] a = {2,2,2,9,7,7,7,3,3,3,6,6,6,0,0,0};
int len = a.length;
char[][] s = new char[len][];
int maxlen = 0; //用统计每一行的最大长度
for(int i=0; i<len; i++){//转换为三进制
s[i] = new StringBuffer(Integer.toString(a[i], 3)).reverse().toString().toCharArray();
//将字符串逆转是为了下面更好计算
if(s[i].length > maxlen) maxlen = s[i].length;
}
int[] res = new int[maxlen];
for(int i=0; i<len; i++){//不进位的加法
for(int j=0; j<s[i].length; j++){
res[j] = res[j] + (s[i][j] - '0');
res[j] = res[j]%3;
}
}
int sum = 0;
for(int i=0; i<maxlen; i++){//将三进制转换为十进制
sum += res[i]*Math.pow(3, i);
}
System.out.println(sum);
}
}
上面提到的算法和技巧参考书籍如下:《程序员面试金典》《挑战程序设计竞赛》《程序员代码面试指南》《剑指offer》《编程之美》《算法导论》