基础知识
问题:
- 与:都为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 数组中唯一成对的数
1-1000这1000个数放在含有1001个元素的数组中,只有唯一的一个元素值重复,其他均只出现一次。每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空间,能否设计一个算法实现?
思路:对于任何数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)
给定 N=100311,则有 2 个 0,3 个 1,和 1 个 3。
原题链接:https://pintia.cn/problem-sets/994805046380707840/problems/994805143738892288
思路:开辟一个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的个数
例:9的二进制表示为1001,有2位是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的整数次方
用一条语句判断一个整数是不是2的整数次方(二进制中只有一个1)
思路:-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 浮点实数的二进制表示
给定一个介于0和1之间的实数,(如0.625),类型为double,打印它的二进制表示(0.101,小数点后的二进制分别表示0.5,0.25,0.125…)。如果该数字无法精确地用32位以内的二进制表示,则打印“ERROR”。
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)
网络世界中时常会遇到这类滑稽的算命小程序,实现原理很简单,随便设计几个问题,根据玩家对每个问题的回答选择一条判断树中的路径(如下图所示),结论就是路径终点对应的那个结点。
现在我们把结论从左到右顺序编号,编号从 1 开始。这里假设回答都是简单的“是”或“否”,又假设回答“是”对应向左的路径,回答“否”对应向右的路径。给定玩家的一系列回答,请你返回其得到的结论的编号。
题目来自PAT,原题链接:https://pintia.cn/problem-sets/994805046380707840/problems/1336215880692482054
输入格式:
输入第一行给出两个正整数:N(≤30)为玩家做一次测试要回答的问题数量;M(≤100)为玩家人数。
随后 M 行,每行顺次给出玩家的 N 个回答。这里用 y 代表“是”,用 n 代表“否”。
输出格式:
对每个玩家,在一行中输出其对应的结论的编号。
输入样例:
3 4
yny
nyy
nyn
yyn
输出样例:
3
5
6
2
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 子集生成
编写一个方法,返回某集合的所有非空子集
给定一个int数组A和数组的大小int n,请返回A的所有非空子集
保证A的元素个数小于等于20,且元素互异
加or不加 例如{A,B,C}
{}
加不加A {A} {}
加不加B {A,B} {A} {B} {}
加不加C {A,B,C}{A,B} {A,C}{A} {B,C}{B} {C}{}
思路:选不选=>两种状态
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 部分和
给定整数序列a1,a2,…an,判断是否可以从中选出若干数,使它们的和恰好为k
1<=n<=20
-10 ^ 8<=ai<=10 ^ 8
-10 ^ 8<=k<=10 ^ 8
样例:
输入:
n=4
a={1,2,4,7}
k=13
输出:
Yes (13 = 2 + 4 +7)
相当于子集生成问题,求出符合条件的子集
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次和出现一次
数组中只有一个数出现了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,3,9,27,81,…等3的指数幂神奇之处在于用它们的组合可以称出任意整数的重量(砝码允许放在左右两个盘中)。
本题目要求编程实现:对用户给定的重量,给出砝码组合方案,重量<1000000
例如:
用户输入:
5
程序输出:
9-3-1
思路: 进制解法 余1–取 余0–不取 -1–取 余2改为-1
19/3=6…1
6/3=2…0
2/3=0…2 --> 2/3=1…-1(商++,余数变为-1)
1/3=0…1
1 0 -1 1 <–> 1 3 9 27
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); // 去掉前面的符号
}
}