基础知识
问题:
- 与:都为1结果为1,或:有一个为1结果为1,异或:二者不同是结果为1
- 判断奇偶数: x&1=1,x为奇数;x&1=0,x为偶数
- 获取二进制位是1还是0(两种解决方案) &运算
- 交换两个整数变量的值 做三次异或运算(异或的概念)
- 不用判断语句,求整数的绝对值
- 在处理整型数值时,可以直接对组成整型数值的各个位进行操作。这意味着可以使用屏蔽技术获得整数中的各个位
- &(与)、|(或)、^(异或)、~(非/取反)
- <<和>>运算符将二进制进行左移或者右移操作
- 运算符>>>将用0填充高位;运算符>>用符号位填充高位,没有<<<运算符
- 对于int型,1<<35与1<<3是相同的,而左边的操作数是long型时需对右侧操作数模64
异或:
- 可以理解为不进位加法:1+1=0,0+0=0,1+0=1
性质:
- 交换律 可任意交换运算因子的位置,结果不变
- 结合律 (即(a^b) ^ c==a ^ (b^c))
- 对于任何数x,都有x ^ x=0. x ^ 0=x,同自己求异或为0,同0求异或为自己
- 自反性A ^ B ^ B=A ^ 0= A,连续同一个因子做异或运算,最终结果为自己
二进制-运算题目
1 找出落单的数
思路:对于任何数x,都有x ^ x = 0. x ^ 0 = x,同自己求异或为0,同0求异或为自己
public class Main {
public static void main(String[] args) {
int N=11;
int[] arr=new int[]{1,2,3,4,5,1,2,3,6,5,6};
int x1=0; // 0和自己异或为0, 初始化为0
for (int i = 0; i < N; i++) {
x1=x1^arr[i]; // 自己同自己异或为0,则相同的会消去
}
System.out.println(x1);
}
}
2 数组中唯一成对的数
思路:对于任何数x,都有x ^ x = 0. x ^ 0 = x,同自己求异或为0,同0求异或为自己。两次异或
public class arrDoubleNumber {
public static void main(String[] args) {
// 法一:两次异或
int N = 1001;
int[] arr = new int[N];
for (int i = 0; i < arr.length - 1; i++) {
arr[i] = i + 1;
}
// 最后一个数是随机数
arr[arr.length - 1] = new Random().nextInt(N - 1) + 1;
int index = new Random().nextInt(N);
int x1 = 0;
for (int i = 1; i <= N - 1; i++) {
x1 = x1 ^ i;
}
for (int i = 0; i < N; i++) {
x1 = x1 ^ arr[i];
}
System.out.println(x1);
// 法二:开辟辅助空间
int[] helper = new int[N];
for (int i = 0; i < N; i++) {
helper[arr[i]]++;
}
for (int i = 0; i < N; i++) {
if (helper[i]==2){
System.out.println(i);
}
}
}
}
总结:在开辟辅助变量的方法中,可利用数组对应的下标来记录对应的值来进行增减记录
扩展:个位数统计(PAT)
思路:开辟一个10的数组,遍历输入的每一个数据,在数组对应的位置做标记(++操作)。
也可以利用java中Map结构记录键和值
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
int[] arr = new int[10];
for (int i = 0; i < s.length(); i++) {
arr[s.charAt(i) - '0']++; // 标记,每出现一次++
}
for (int i = 0; i < arr.length; i++) {
if (arr[i] != 0) {
System.out.println(i + ":" + arr[i]);
}
}
}
}
3 判断二进制中的1的个数
思路:1左移、变量右移、-1与本身做&运算(每次消掉一个1)
x每减一次1&x本身就会消去一个1,可自行找数据测试
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("请输入一个整数:");
int x = sc.nextInt();
String y = toBinary(x);
int count = 0;
// 法一:左移
/*for (int i = 0; i < 32; i++) {
if (((1<<i) & x) == (1<<i)) {
count++;
}
}*/
// 法二:右移
/*for (int i = 0; i < 32; i++) {
if (((x >> i) & 1) == 1) {
count++;
}
}*/
// 法三:-1和本身做&运算
while (x != 0) {
x = ((x - 1) & x);
count++;
}
System.out.println("二进制为" + y);
System.out.println("1的个数有" + count);
}
// 求二进制
public static String toBinary(int num) {
String res = "";
while (num != 0) {
int r = num % 2;
res = r + res;
num /= 2;
}
return res;
}
}
4 判断是不是2的整数次方
思路:-1与本身做&运算(每次消掉一个1)
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个整数:");
int x=sc.nextInt();
if (((x-1)&x)==0){ // 消去之后为0说明只有一个1
System.out.println("是2的整数次方");
}else {
System.out.println("不是");
}
}
}
5 交换奇偶位数
将整数的奇偶位互换 0b二进制 0x十六进制
思路:取出1的位置 偶数位右移,奇数位左移
public class Main {
public static void main(String[] args) {
int a=0b01000000_00000000_00000000_00000000;
System.out.println(a);
System.out.println(each(6));
}
public static int each(int i) {
int ou = i & 0xaaaaaaaa;//和1010 1010 1010 ...做与运算取出偶数位 0x十六进制
int ji = i & 0x55555555;//和0101 0101 0101 ...做与运算取出奇数位 0x十六进制
return (ou >> 1) ^ (ji << 1);//连起来
}
}
6 浮点实数的二进制表示
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个小数转为二进制:");
double num = sc.nextDouble();
StringBuilder sb = new StringBuilder("0.");
while (num > 0) {
num *=2;
if (num >= 1) {
sb.append("1");
num -= 1.0;
} else {
sb.append("0");
}
if (sb.length()>34){
System.out.println("ERROR");
return;
}
}
System.out.println(sb.toString());
}
}
二进制-选择题目
当面对有两种选择的题目时,均可用二进制表示两种状态(0,1)
1 前世档案(PAT)
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
//是-->0 否-->1
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 流
String[] s = br.readLine().split(" "); // 读取控制台一行数据,并进行切分
int n = Integer.parseInt(s[0]);
int m = Integer.parseInt(s[1]);
for (int i = 0; i < m; i++) {
StringBuilder sb = new StringBuilder();
String ss = br.readLine(); // 读取控制台一行数字
for (int j = 0; j < ss.length(); j++) { // 遍历,如果为n则添加1,y添加0
if (ss.charAt(j) == 'n') {
sb.append('1');
} else if (ss.charAt(j) == 'y') {
sb.append('0');
}
}
String res = sb.toString();
int count = 0; // 累计平方
int sum = 0; // 累加和
for (int j = res.length() - 1; j >= 0; j--) { // 二进制转十进制
int tem = res.charAt(j) - 48; // 当前位为0or1
int pow = (int) Math.pow(2, count++); // 2^count
sum += tem * pow;
}
System.out.println(sum+1); // 从0开始,+1
}
}
}
2 子集生成
思路:选不选=>两种状态
public class Main {
public static void main(String[] args) {
int[] A = {1, 2, 3};
ArrayList<ArrayList<Integer>> subsets = getSubsets(A, A.length);
System.out.println(subsets);
}
//二进制法(模板) 1 0 代表选不选两种状态
/*
1选0不选
A B C
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1
number 中二进制为1,s.add(对应的元素)
*/
public static ArrayList<ArrayList<Integer>> getSubsets(int[] A, int n) {
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
for (int i = (int)Math.pow(2, n) - 1; i > 0; i--) {
ArrayList<Integer> ints = new ArrayList<>(); // 对每个i建立一个集合
for (int j = n - 1; j >= 0; j--) { // 检查哪个位上的二进制为1
if (((i >> j) & 1) == 1) {
ints.add(A[j]);
}
}
res.add(ints);
}
return res;
}
}
3 部分和
相当于子集生成问题,求出符合条件的子集
import java.util.ArrayList;
public class Main {
static int k = 13;
static int n = 4;
public static void main(String[] args) {
int[] A = {1, 2, 4, 7};
boolean flag = getSubsets(A, n);
if (!flag) {
System.out.println("No");
}
}
public static boolean getSubsets(int[] A, int n) {
for (int i = (int) Math.pow(2, n) - 1; i > 0; i--) {
ArrayList<Integer> ints = new ArrayList<Integer>(); // 对每个i建立一个集合
for (int j = n - 1; j >= 0; j--) { // 检查哪个位上的二进制为1
if (((i >> j) & 1) == 1) {
ints.add(A[j]);
}
}
int res = 0;
for (Integer anInt : ints) {
res += anInt; // 累加
}
if (res == k) { // 符合条件
// 输出
System.out.print("Yes(" + k + "=");
for (int j = ints.size() - 1; j >= 0; j--) {
if (j == 0) System.out.println(ints.get(j) + ")");
else System.out.print(ints.get(j) + "+");
}
return true;
}
}
return false; // 表示找不到
}
}
二进制-变种问题
1 出现k次和出现一次
知识应用:
2个相同的2进制做不进位加法,结果为0
10个相同的10进制数做不进位加法,结果为0
k个相同的k进制数做不进位加法,结果为0
// 二进制解法
public class Main {
public static void main(String[] args) {
int[] arr = new int[]{2, 2, 2, 10, 7, 7, 7, 3, 3, 3, 6, 6, 6, 0, 0, 0};
int len = arr.length;
char[][] kRadix = new char[len][];
int k = 3;
// 转成k进制字符数组,保存最大位数
int maxlen = 0;
// 对于每个数字
for (int i = 0; i < len; i++) {
// 求每个数字的三进制字符串,然后转为字符数组
// reverse():翻转一下,保证位数对齐
kRadix[i] = new StringBuilder(Integer.toString(arr[i], k)).reverse().toString().toCharArray();
if (kRadix[i].length > maxlen) { // 求出最大位数
maxlen = kRadix[i].length;
}
}
int[] resArr = new int[maxlen];
for (int i = 0; i < len; i++) {
// 不进位加法
for (int j = 0; j < maxlen; j++) {
if (j >= kRadix[i].length) {
resArr[j] += 0; // 补0
} else {
resArr[j] += (kRadix[i][j]-'0');// -'0'是底层仍是字符
}
}
}
// 转为10进制
int res = 0;
for (int i = 0; i < maxlen; i++) {
res += (resArr[i] % k) * (int) (Math.pow(k, i));
}
System.out.println(res);
}
}
2 天平称重问题(蓝桥杯)
思路: 进制解法 余1–取 余0–不取 -1–取 余2改为-1
public class Main {
public static void main(String[] args) {
for (int i = 1; i < 20; i++) {
System.out.println(i + ":" + f(i));
}
}
// 进制解法 变种三进制
public static String f(int x) {
String s = "";
int q = 1; // 权重
while (x > 0) {
int sh = x / 3; // 商
if (x % 3 == 1) {
s = "+" + q + s;
} else if (x % 3 == 2) {
sh++;
s = "-" + q + s;
}
x = sh;
q *= 3;
}
return s.substring(1); // 去掉前面的符号
}
}