问题
给你一个int,把他的二进制完全倒换过来,然后再输入int
问题参考:190. 颠倒二进制位
方案
1. 字符串操作:很容易想到,操作字符串跟直观
2. 字符数组操作,都可以使用位操作来替代,比如获取哪一位(& 0000 0010), 将结果累加上 (0 | 0010 ),或者增量(0000 0010 << 1, 就可以获取第三位)
3. 读取原始int值二进制最后一位,挨个放在result结果的最高位
4. 读取原始int值二进制最后一位,挨个放在result的最后一位,放之前全部享有移动一个位
5. 字符串跌倒还有种分支算法,即前后字符串跌倒。类似二进制也可以。
1.字符串操作
其中用到技术点
1. 交互char数值,可以参考:位运算.异或概念,性质,应用(交换数值,序列只有一个数重复,序列只有一个不重复)_要钱也要自我实现-CSDN博客
2. 获取int类型的二进制字符串:Integer.toBinaryString(n)
3. 将二进制转换为int:(int) Long.parseLong(binary,2)。其中Integer.valueOf("10111111111111111111111111111111",2)首字母为1的会解析报错,就使用了long解析函数
/**
* 1.字符串操作
* @param n
* @return
*/
public int reverseBits1(int n) {
String binary = String.format("%32s", Integer.toBinaryString(n)).replaceAll(" ", "0");
char[] chars = binary.toCharArray();
for(int i = 0,j=chars.length-1 ;i<j;i++,j--){
// 交互
chars[i] = (char) (chars[i] ^ chars[j]);
chars[j] = (char) (chars[i] ^ chars[j]);
chars[i] = (char) (chars[i] ^ chars[j]);
}
binary = new String(chars);
/**
* 32个二进制位
* Integer.valueOf("10111111111111111111111111111111",2)
* 首字母为1的会解析报错
*/
return (int) Long.parseLong(binary,2);
}
2.位操作:始终读取int最后一位,从高位到低位挨个放
将二进制按照字符串存储,很浪费内存。
截取最后一位,然后移动到对应位置,跟result(init=0)或
图示
代码
/**
* 2.位操作:始终读取int最后一位,从高位到低位挨个放
* - 将二进制按照字符串存储,很浪费内存。
* - 截取最后一位,然后移动到对应位置,跟result(init=0)或
* @param n
* @return
*/
public int reverseBits2(int n) {
// 结果变量 每次出来的二进制位“拼接”进去
int result = 0;
// int的二进制位数
int len = 31;
// 中间变量存储
int temp = 0;
while(len >= 0 && n != 0){
// 获取最后一位
temp = n & 1;
// 向左移动
temp = temp << (len--);
// 拼接到结果上
result = result | temp;
// 去掉最后一位
n = n >>> 1;
}
return result;
}
3.位操作变种:始终读取int最后一位,每次放在第一位
图示
代码
/**
* 3.位操作变种
* - 第二种是将最后1位 左移到指定位置,这种是 每次只将结果左移一位,用来放截取的最后一位
* @param n
* @return
*/
public int reverseBits3(int n) {
// 结果变量 每次出来的二进制位“拼接”进去
int result = 0;
// int的二进制位数
int len = 31;
// 中间变量存储
int temp = 0;
while(len >= 0){
// 获取最后一位
temp = n & 1;
// 向左移动一位
result = result << 1;
// 拼接到结果最后一位上
result = result | temp;
// 去掉最后一位
n = n >>> 1;
// 递减
len --;
}
return result;
}
4.分治+位操作
- 对比第二种和第三种 遍历次数基本是32次
- 改善:一位一位操作太麻烦,能否一次操作几个二进制位。
加上分治算法思想,AB字符串反过来就是BA,如果A或者B也是字符串,前后颠倒就行,好在这里32是2的4次方,固定的,不需要迭代,可以直接写死。
图示
代码
1. 如果是字符串这么搞,是从:前16和后16位换,前16的前8位和16的后8位换,以此类推;
2. 二进制也可以这么搞,但会出现很多中间变量
3. 优化结果是:从下往上,奇偶互换,两位两位互换,四位四位互换,…… ,前16位和后边16位互换
public int reverseBits(int n) {
final int M1 = 0x55555555; // 01010101010101010101010101010101
final int M2 = 0x33333333; // 00110011001100110011001100110011
final int M4 = 0x0f0f0f0f; // 00001111000011110000111100001111
final int M8 = 0x00ff00ff; // 00000000111111110000000011111111
final int M16 = 0x0000ffff; // 00000000000000001111111111111111
/**
* n >>> 1 & M1
* n >>> 1: 偶数位 移动 奇数位
* n >>> 1 & M1 : 提取奇数位
*
* (n & M1) << 1
* n & M1: 提取奇数位
* (n & M1) << 1:奇数位 移动到偶数位
*
* n >>> 1 & M1 | (n & M1) << 1
* 奇偶互换
*/
n = n >>> 1 & M1 | (n & M1) << 1;
/**
* 同理:32位,两两一组,类似奇偶互换一样
* 四个四个一组
* 八个八个一组
*/
n = n >>> 2 & M2 | (n & M2) << 2;
n = n >>> 4 & M4 | (n & M4) << 4;
n = n >>> 8 & M8 | (n & M8) << 8;
/**
* 前16和后16互换
* n >>> 16 | n << 16
*/
return n >>> 16 & M16 | (n & M16) << 16;
}
总结
1. 对int的二进制操作,逻辑上可以转为二进制char[]处理的,都可以按照位操作实现
2. 对int的二进制操作,优先考虑位操作。相对于char[]的操作,位操作更简单,比如:前16和后16互换n = n >>> 16 | n << 16