位运算在算法中是一种很常见的运算
位运算的基本讲解
它具有一下性质:
1.交换律,可以任意交换运算因子,结果不变
2.结合律,即(a ^ b) ^ c = a ^(b ^ c);
3.对于任何数x,都有x ^ x = 0 ,x ^ 0 = x,同自己求异或为0,同0求异或为自己
4.自反性 A ^ B ^ B = A ^ 0 = A,连续和同一个因子做异或运算,最终结果为自己
下面对位运算符进行一定的说明:
1.在处理整形数值时,可以直接对组成整形数组的各个位进行操作。
2.&(与)、|(或)、^(异或)、~(非/取反)
3.>>和<<运算符将二进制位进行右移或者左移操作
3.>>>运算符将0填充高位;>>运算符用符号位填充高位,没有<<<运算符
4.对于int型,1<<35与1<<3是相同的,而左边的操作数是long型时需对左侧操作数模64
5.与:都为1结果为1,或:有一个为1结果为1,异或:二者不同时结果为1
下面用一些位运算的例子来对为运算的解题套路来进行说明:
题1:找出唯一成对的数
题目讲解:
这道题中所述的题目立意是:数组里1-1000数中有999个独立的,有一个数在数组中出现两次,而我们就是需要设计算法来找到这个出现两次的数,尽可能地可以使算法优化,解决这种方法,我们提供两种解法:
解法一:采用位运算的方法,前面我们说过自己和自己异或为0,但这题中如果把数组里的元素全部异或一遍,很显然是无法得出结果的,因为只有那个出现两次的数会消掉,其他的数并不能消掉,所以我们就得想办法拼凑,咋们将先将1-1000的数都异或一遍得到的结果,再和数组里的元素进行异或,这样的话,原本出现一次的数经过此操作之后就相当于出现了两次,都消掉了,而原本出现两次的数就相当于出现了三次,仍会保留,故异或的结果就是出现两次的那个数。
解法二:创建一个容量为1001的数组,当出现k时就将下标为k的元素++,统计出下标1-1000的元素中为2的元素。输出该元素的下标
下面我们来展示一下代码:
package 找到唯一成对的数;
import java.util.Random;
import java.util.Scanner;
public class Test1 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int nums[] = new int[1001];
for (int i = 0; i < 1000; i++) {
nums[i] = i + 1;
}
nums[nums.length - 1] = new Random().nextInt(1000) + 1;
// for (int i = 0; i < 1001; i++) {
// nums[i] = sc.nextInt();
// }
int index = new Random().nextInt(1001);
swap(nums, index, nums.length - 1);
System.out.println("==============");
// 方法一:按照异或运算来解决
int result = 0;
for (int i = 0; i < 1000; i++) {
result ^= (i + 1);
}
for (int i = 0; i < 1001; i++) {
result ^= nums[i];
}
sc.close();
System.out.println(result);
System.out.println("==============");
// 方法二:放入数组中
int[] arr = new int[1000];
for (int i = 0; i < 1001; i++) {
arr[nums[i] - 1]++;
}
for (int i = 0; i < 1000; i++) {
if (arr[i] == 2) {
System.out.println(i + 1);
break;
}
}
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
题2:找到落单的那个数
解题思路:
这题跟上一题的解题思路一样,我们就不做过多的解释了,直接异或数组即可,或者采用数组存储的方法
下面我们来展示一下代码:
package 找到落单的那个数;
import java.util.Random;
public class Test2 {
public static void main(String[] args) {
// 随机创建落单的数
int N = 999;
Random r = new Random();
int n = r.nextInt(500);
int nums[] = new int[N];
for (int i = 0; i < 500; i++) {
if (i < n) {
nums[2 * i] = nums[2 * i + 1] = i;
} else if (i == n) {
nums[2 * i] = i;
} else {
nums[2 * i - 1] = nums[2 * i] = i;
}
}
int result = 0;
System.out.println("============");
// 方法一:异或运算
for (int i = 0; i < 999; i++) {
result ^= nums[i];
}
System.out.println(result);
System.out.println("=============");
// 方法二
int arr[] = new int[500];
for (int i = 0; i < 999; i++) {
arr[nums[i]]++;
}
for(int i=0;i<500;i++){
if(arr[i]==1){
System.out.println(i);
break;
}
}
}
}
题3:二进制中一的个数
解题思路:
该题采用与运算来解题,给出三种解题思路
解法一:将该数N与1进行与运算,若结果为1,则说明该数的末尾为1,若为0,则该数末尾为0,判断之后将N右移来判断次低位是否为1,以此类推,直到N为0时停止循环,统计出1的个数
解法二:不将N右移,将1左移也可以得出1的个数
解法三
:第三个解法也是最优化的解法,首先我们需要直到N&(N-1)可以消除N最末尾的1,无法理解的小伙伴可以在草稿纸上举个例子试试,那么我们就可以让N=N&(N-1)直到N为0时,在N为0之前进行了几次上式操作,就说明N内有几个1
下面我们来展示一下代码:
package 二进制中一的个数;
import java.util.Scanner;
public class Test3 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int n = 0;
// 解法1:
// 任何数与1做与运算,若末尾为1则为1,若末尾为0则为0
// 将N向左移
while (N != 0) {
// 求末尾是否为1
if ((N & 1) == 1) {
n++;
}
N >>= 1;
}
System.out.println(n);
// System.out.println("==============");
// // 解法2:
// // 将1向右移
// n = 0;
// for (int i = 0; i < 32; i++) {
// if ((N & (1 << i)) == (1 << i)) {
// n++;
// }
// }
// System.out.println(n);
// System.out.println("==============");
// // 解法3:
// n=0;
// while(N!=0){
// // N&(N-1)可以找出最低位的1
// N = N&(N-1);
// n++;
// }
// System.out.println(n);
}
}
题4:是不是2的整数次方数
解题思路:
我们仔细观察可以发现2的整数次方数都由一个规律,那就是他们转化为2进制表达时,都有且仅有一个1,这是个充要条件,联系到上题我们可以得知N&(N-1)
可以得出N最低位的1,如果应用到这题中N&(N-1)==0
的话即可说明N中有且仅有一个1,那么这个数也是2的整数次方数。
下面我们来展示一下代码:
package 是不是2的整数次方;
import java.util.Scanner;
public class Test4 {
public static void main(String[] args) {
// 若该整数的二进制数值中只有一个1组成,那么这个数就是2的整数次方
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
if((n&(n-1))==0){
System.out.println("该数是2的次方数");
}else{
System.out.println("该数不是2的次方数");
}
}
}
题5:将整数的奇偶位互换
解题思路:
做与运算将奇数位和偶数位分离出来
要将奇数位分离出来 该数与0101…求与运算
要将偶数位分离出来 该数与1010…求与运算
将整数的最大范围32位全部进行与运算
下面我们来展示一下代码:
package 将整数的奇偶位互换;
import java.util.Scanner;
import javax.swing.event.SwingPropertyChangeSupport;
public class Test5 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
// 做与运算将奇数位和偶数位分离出来
// 要将奇数位分离出来 该数与0101...求与运算
// 要将偶数位分离出来 该数与1010...求与运算
// 将整数的最大范围32位全部进行与运算
int ji = N&(0x55555555);
int ou = N&(0xaaaaaaaa);
// 再将奇数和偶数位移位之后用异或运算或者是或运算组合
int n = (ji<<1)|(ou>>1);
System.out.println(n);
}
}
题6:0~1间浮点实数的二进制表示
解题思路:
将该数乘以2,当其大于1时,将字符串里面加入“1”,并且将该数减去1,当小于1时,直接向字符串中加入“0”,直到该数为0,或者位数超过32位结束
下面我们来展示一下代码:
package 浮点实数之间的二进制表示;
import java.util.Scanner;
public class Test6 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
double N = sc.nextDouble();
int flag = 0;
StringBuffer sb = new StringBuffer("0.");
while(N != 0){
N *= 2;
if(N>=1){
sb.append("1");
N -= 1;
}else{
sb.append("0");
}
// 32位加上"0."所以不能大于34位
if(sb.length()>=34){
flag = 1;
System.out.println("ERROR");
return;
}
}
if(flag == 0){
System.out.println(sb);
}
}
}
题7:出现k次与出现1次
解题思路:
在做这题之前我们回顾一下,前面的找到“落单的那个数”,其实如果k为偶数的话这题还是比较简单的直接异或就可以了,但如果k为奇数呢?
所以这道题我们就不可以用往常的套路进行异或运算,我们思考一下,当两个相同的二进制数每一位都做不进位加法那么得出的结果每一位都一定为0,当十个相同的十进制每一位都做不进位加法那么每一位都一定为0,所以我们可以总结出来当k个相同的数转化为k进制做不进位加法那么结果也一定为0
那么相对于我们这题,我们只要加所有的数转化为k进制再对其各位进行不进位加法,那么最后加出来的结果就一定是出现一次的那个数的k进制,我们再将其转化为十进制即可
扩展:Integer.toString(num,k)函数可以将num直接转换为k进制的字符串
下面我们来展示一下代码:
package 出现k次与出现1次;
import java.util.Scanner;
public class Test7 {
public static void main(String[] args) {
// k为偶数的话直接异或即可,k为奇数的话怎么办?
// 2个相同的2进制数做不进位加法结果为0,
// k个相同的k进制数做不进位加法结果为0
// 所以这题只需要将该数转化为k进制做不进位加法,
// 然后再将结果转化为10进制即可求出答案
Scanner sc = new Scanner(System.in);
int N = 10;
int[] nums = {2, 2, 2, 9, 7, 7, 7, 3, 3, 3};
// System.out.println(nums.length);
char[][] kRandix = new char[N][];
int k = 3;
// for (int i = 0; i < N; i++) {
// nums[i] = sc.nextInt();
// }
// StringBuffer[] sb = new StringBuffer[10];
// Integer.toString()可以直接将十进制转化成k进制的字符串
int maxLen = 0;
for (int i = 0; i < 10; i++) {
kRandix[i] = new StringBuilder(Integer.toString(nums[i], k)).reverse().toString().toCharArray();
if (kRandix[i].length > maxLen) {
maxLen = kRandix[i].length;
}
}
// 不进位加法
int[] resArr = new int[maxLen];
for (int i = 0; i < N; i++) {
for (int j = 0; j < maxLen; j++) {
if (j >= kRandix[i].length) {
resArr[j] += 0;
} else {
resArr[j] += (kRandix[i][j] - '0');
while (resArr[j] / k != 0) {
resArr[j] -= k;
}
}
}
}
int res = 0;
for (int i = 0; i < maxLen; i++) {
res += resArr[i] * (int)Math.pow(k, i);
}
System.out.println(res);
}
}