# 【蓝桥杯备赛】:1.位运算

前言:请遵循 ==问题->思路->实践->题解==的方式学习

位运算的重要性质

& (与)

与的有趣性质
  • 判断奇数还是偶数
    任何数只要和1(0x00000001)进行&,如果等于1则是奇数,如果等于0,就是偶数。

因为和1相&,除了第一位外,其他位都变成了0,而且二进制判断奇偶数的办法就是看第一位是零还是1;

性质运用
例题1

找到二进制中1的个数

1
2
3
4
5
6
7
8
9
10
public static int countOne(int n){
int count = 0;
while(n != 0){
if(n & 1==1){
count++;
}
n = n >>> 1;
}
return count;
}
例题2

判断是否是二的整数次方

1
2
3
public static boolean isPowerOfTwo(int n){
return (n>0) && ((n& (n-1))==0);
}

因为这里如果是二的整数次方的话,有最高位为1其他位为零的性质,那如果它减去1的话,就变成全是1的数了。刚好&,就变成0;

^ (异或)

异或的运算

对于两个二进制数 1010 和 1100,进行异或运算的结果是 0110。这是因为在进行异或运算时,每一位上==不同的数会产生一个1,而相同的数会产生一个0==。

异或的有趣性质

相同的数异或为零,零和任何数异或都为任何数本身。
==二进制由于除了1就是0,可以用异或来表示不进位的加法,但是其他进制的不进位加法就不是异或了==

性质运用
  • 如果有一个数组中只有一个数字出现了偶数次,其他数字都出现了奇数次,那么对数组中的所有数字进行异或操作,最终的结果就是那个出现偶数次的数字。
例题1
  • 有一个1-1000 的数组,只有唯一的元素值重复,其他均只是出现一次。每个数组元素只能被访问一次,请设计一个算法把它找出来,注意:不可以使用辅助空间。

如果可以使用辅助空间,没有什么限制条件的话,其实还有一种解法:就是用放到容器里,以值为下标,以出现的数量为容器的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class 唯一成对的数 {

public static void main(String[] args) {
int N = 11;
int[] arr = new int[N];
for (int i = 0; i < arr.length; i++) {
arr[i] = i + 1;
}
//随机生成一个数
arr[N - 1] = new Random().nextInt(N - 1) + 1;
int index = new Random().nextInt(N);
Util.swap(arr, index, arr.length - 1);
Util.print(arr);

solution(arr);
}

//这里用到异或的重要性质,相同的数异或为零,任何数字与零异或都是它本身。
public static void solution(int[] arr) {
int res = 0;
System.out.println(arr.length);
for (int i = 1; i <= arr.length - 1; i++) {
res = (res ^ i);
}
for (int i = 0; i < arr.length; i++) {
res = arr[i] ^ res;
}


System.out.println(res);
}
}
例题2
  • 不用if语句求绝对值
1
2
3
4
public static int abs(int num){
int mask = num >> 31;
return (num + mask) ^ mask;
}

注意:对于负数的>>31,其符号位为1,向右移动31位得到0x11111111,右移是补1的,结果就是-1.而且为啥负数要加-1呢?因为==补码表示法==的特点是:正数的补码和原码相同。
负数的补码是其对应正数的按位取反后加1。

例题3
  • 交换两个整数的值
1
2
3
4
5
pubic static void swap(int a, int b){
int a = a ^ b;
int b = a ^ b;
int a = a ^ b;
}

这里用到了性质是交换律,异或连续和同一因子做异或运算,最终结果为自己。
A^B相当于==激发态==,如果再有原来的两数之一进行异或就会变回来那个两个数之一。

综合

交换奇偶位

指的是把奇数位和偶数位上的值作交换。这里也可以用多一个辅助空间解决。就是先转换成字符数组,然后循环交换n和n+1,每次n+2;但是这里使用位运算可以更加简单。

1
2
3
4
5
public static int swapOddEvenBits(int i){
int odd = i & 0xAAAAAAAA;
int even = i & 0x55555555;
return add ^ even;
}

这里的是利用了&运算的保留作用:可以通过&1保留,&0去除,取出奇数位和偶数位。然后再通过异或的不进位加法的原理进行连接。

打印二进制的0~1之间的浮点实数表示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args){
double num = 0.625;
StringBuilder sb = new StringBuilder("0.");
while(num>0){
double r = num *2;
if(r >= 1){
sb.append("1");
//消除整数部分
num = r -1;
}else{
sb.append("0");
num = r;
}
}
System.out.println(sb);
}

对于原始的二进制,比如15 ,是除以2,余数为1,商7,然后再把商进行一样的操作,把每个1和0组合在一起得到结果。

出现一次和出现k次

数组中只有一个数出现了1次,其他数字都出现了k次。请输出出现了1次的数。

==k个相同的k进制数作不进位的加法结果为0==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class _11出现k次 {
public static void main(String[] args) {
int[] arr = {2, 2, 2, 9, 7, 7, 7, 3, 3, 3, 6, 6, 6, 0, 0, 0};
int len = arr.length;
/** 这一行代码则是创建了一个二维字符数组 res,其中 res 的第一维长度为 len。
但是,第二维的长度没有指定,因此第二维的长度将根据具体情况动态确定**/
char[][] res = new char[len][];
int k = 3;
int maxlength = 0;
//转化成k进制的数组
for (int i = 0; i < len; i++) {
res[i] = new StringBuilder(Integer.toString(arr[i], k)).reverse().toString().toCharArray();
if (res[i].length > maxlength) {
maxlength = res[i].length;
}
}

//遍历二维字符数组作不进位加法
int[] resArr = new int[maxlength];
for (int i = 0; i < len; i++) {
//不进位加法
for (int j = 0; j < maxlength; j++) {
if (j >= res[i].length) {
resArr[j] += 0; // 不进位加0
} else {
resArr[j] += (res[i][j] - '0'); // 不进位加当前位的数字
}
}


}
int res1 = 0;
for (int i = 0; i < maxlength; i++) {
res1 += (resArr[i] % k) * Math.pow(k, i);
}
System.out.println(res1);
}
}

不规则进制才是真实的世界