算法之二进制巧算

基础知识

问题:

  • 与:都为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); // 去掉前面的符号
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明仔爱编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值