目录
位运算篇
1. 找出唯一成对的数
1-1000 这 1000 个数放在含有 1001 个元素的数组中,只有唯一的一个元素值重复,其它均只出现依次。每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空间,能否设计一个算法实现?
分析:
方法一:位运算
在位运算中,我们知道两个相同的数进行异或运算结果为 0,任何数与 0 进行异或运算结果为它本身。即 N ^ N = 0,N ^ 0 = N
已知在 1001 个数中,有 1-1000 这 1000 个数和唯一一个重复的数,那我们可以利用上述性质,我们可以先用 0 去和 1-1000 的数进行异或运算,得到的结果再与数组中的 1001 个数进行异或运算,最后得到的结果就是重复的元素,如下图所示:
代码:
import java.util.Random;
public class Test {
public static void main(String[] args) {
//1. 先创建一个容量为 1001 的数组
int n = 1001;
int[] nums = new int[n];
//2. 然后依次存入 1-1000 的数
for (int i = 0; i < nums.length - 1; i++) {
nums[i] = i + 1;
}
//3. 最后一个数为随机的重复数字
nums[nums.length - 1] = new Random().nextInt(n - 1) + 1;
//如需查看生成的数组,可自行打印
// for (int i = 0; i < nums.length; i++) {
// System.out.print(nums[i] + " ");
// }
// System.out.println();
//4. 先用 0 去和 1-1000 的数进行异或运算
int num = 0;
for (int i = 1; i <= nums.length - 1; i++) {
num = (num ^ i); // x1为1一直异或到1000
}
//5. 上面得到的结果再与数组中的 1001 个数进行异或运算,最后得到的结果就是重复的元素
for (int i = 0; i < nums.length; i++) {
num = num ^ nums[i];
}
System.out.println(num);
}
}
2. 找出落单的数
一个数组里除了某一个数字之外,其他的数字都出现了两次。请写程序找出这个只出现一次的数字。
分析:
该题和上一题同理,直接来看代码。
代码:
public class Test {
public static void main(String[] args) {
int[] arr = {1,2,1,3,3,5,2};
int result = 0;
for (int i = 0; i < arr.length; i++) {
result ^= arr[i];
}
System.out.println(result);
}
}
结果:
3. 二进制中1的个数
请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。
分析:
方法一:
该题的解法有很多种,我们可以去统计二进制数中 1 的个数等等。但是既然该部分讲述位运算,那么我们就来使用位运算的方法来解决这个问题。
在位运算中,有一种运算方式是与运算,N & 0 = 0,N & 1 = 1 因为 int 类型共有 32 个二进制位,假设当前位置为 x,那我们可以让 1 左移 x 个位置后与当前位置的数进行与运算,最后得到的结果右移 x 个位置看是否和 1 相等,如果相等则 1 的个数加一。
方法二:
第二种方法和第一种方法同理,我们第一种方法是让 1 左移 x 个位置,那现在我们可以让当前位置处的数右移 x 个位置然后与 1 进行与运算,最后的结果和 1 进行与运算,如果等于 1 则个数加一。
方法三:
第三种方法也是使用了位运算,但是和前两种方法不同,我给大家举个例子。假设要统计的数字为 9,其二进制为 1001,1001 减去 1 后得到 1000,然后计算 1000 & 1001 得到 1000;同样的过程我们再来一次,1000 减去 1 后得到 0111,然后计算 0111 & 1000 得到 0000 。我们发现这种过程重复了两次后结果为 0 了,而次数 2 恰好是 1001 中 1 的个数。
由上述可知,(当前数字) & (当前数字 - 1) 重复这个过程,直到结果为 0,那么重复的次数就是该数字对应二进制数中 1 的个数。
注意!!! 负整数采用补码表示
代码:
import java.util.Scanner;
public class Test04 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int num = input.nextInt();
//为了清晰的看到该数的二进制值
System.out.println(Integer.toString(num, 2));
System.out.println(getNum1(num));
System.out.println(getNum2(num));
System.out.println(getNum3(num));
}
//注:负整数采用补码表示
//方法一
public static int getNum1(int num) {
int count = 0;
for (int i = 0; i < 32; i++) {
//(num & (1 << i)) >> i == 1 这是解释中描述的写法
//等价于 (num & (1 << i)) == (1 << i)
if ((num & (1 << i)) == (1 << i)) {
count++;
}
}
return count;
}
//方法二
public static int getNum2(int num) {
int count = 0;
for (int i = 0; i < 32; i++) {
//该处使用无符号右移
if (((num >>> i) & 1) == 1) {
count++;
}
}
return count;
}
//方法三
public static int getNum3(int num) {
int count = 0;
while (num != 0) {
num &= num - 1;
count++;
}
return count;
}
}
结果:
4. 是不是2的整数次方
用一条语句判断一个整数是不是 2 的整数次方
分析:
从上面的题可以看出,该题也可以使用位运算的方法,我们知道 2 的整数次方的值对应的二进制数都是有且只有一个位置的值为 1 ,例如 1、2、4、8、16 等,分别对应的二进制数:00001、00010、00100、01000、10000 等。因此我们可以让需要判断的整数与其减一的值进行与运算,要是结果为 0 ,则表明该数的二进制值有且只有一个位置的值为 1 ,也就是说该数是 2 的整数次方。
代码:
public class Test05 {
public static void main(String[] args) {
int[] nums = {1,2,4,8,16,32,64,128,256,111};
for (int num : nums) {
System.out.println(test(num));
}
}
public static boolean test(int num) {
if (((num - 1) & num) == 0) {
return true;
}
return false;
}
}
结果:
5. 将整数的奇偶位互换
该题要求将一个整数对应的二进制数的奇偶位互换,比如整数 6 ,对应的二进制数位 0110 ,互换后为 1001 ,对应的十进制值为 9 。
分析:
那么这道题应该怎么做呢?我们可以先将整数的奇偶位分别保留出来,我们知道 N & 0 = 0,N & 1 = N 所以我们让整数和 1010 1010 这样的数进行与运算可以得到该数的偶数位,同理让整数和 0101 0101 这样的数进行与运算可以得到该数的奇数位。我们又知道,N ^ 0 = N 所以我们可以将偶数位得到的结果右移一位,再将奇数位得到的结果左移一位,然后将两次移位的结果进行异或运算即可得到该整数奇偶位互换的结果,如下图:
注意:int 位四个字节 32 个二进制位,我们可以将 1010 和 0101 用十六进制表示为 0xaaaaaaaa 和 0x55555555
代码:
public class Test06 {
public static void main(String[] args) {
System.out.println(swap(6));
}
public static int swap(int num) {
int o = num & 0xaaaaaaaa;
int j = num & 0x55555555;
return (o >> 1) ^ (j << 1);
}
}
结果:
6. 0~1间浮点实数的二进制表示
给定一个介于 0 和 1 之间的实数,(如 0.625),类型为 double,打印它的二进制表示 (0.101,因为小数点后的二进制分别表示 0.5,0.25,0.125…) 。如果该数字无法精确地用 32 位以内的二进制表示,则打印 “ERROR” 。
分析:
我们知道十进制整数转换成二进制可以对二进行取余操作,那么十进制的小数如何转换成二进制呢?我们可以对十进制小数乘二取整数部分,直到结果为零结束,例如 0.625 乘 2 得到 1.25 ,取出整数部分 1,然后 0.25 再次乘 2 得到 0.5,取出整数部分 0,然后 0.5 再次乘 2 得到 1,剩余 0 。则 0.625 对应的二进制数为 0.101 。不要忘记题目限制的 32 位以内。
代码:
public class Test {
public static void main(String[] args) {
System.out.println(test(0.625));
System.out.println(test(0.3));
}
public static String test(double num) {
StringBuilder sb = new StringBuilder("0.");
while (num > 0) {
num *= 2;
if (num >= 1) {
sb.append('1');
num = num - 1;
} else {
sb.append('0');
}
}
if (sb.length() > 32) {
return "ERROR";
}
return sb.toString();
}
}
结果:
7. 出现k次与出现1次
数组中只有一个数出现了 1 次,其他的数都出现了 K 次,请输出只出现了 1 次的数。