关于二进制和位运算

一、二进制介绍:  

如果是4位二进制数,第1位为0表示正数,第1位为1表示负数。
5的二进制是: 0101
如何表示-5呢?
将0101先减1,再取反,得到:1011 所以1011就表示-5
 
如何知道一个负数表示负几呢?先取反,再+1,比如:
1011为-5,得到他的正数就是:取反为0100,再+1,得到:0101,为5,所以就是-5啦。
 
如何表示-8?
正8为:1000,减1得到:0111,再取反得到:1000,所以-8就是1000
依次得到:
-8: 1000
-7: 1001
-6: 1010
..
-1: 1111
0: 0000
..
7: 0111
 
为什么-1是1111呢?首先最高位为1表示是负的,负几呢?先取反,再+1:
取反为:0000,+1为0001,所以就是-1
 
总结:
正 -> 负,先-1,再取反
负 -> 正,先取反,再+1
 
如果只在4位数讨论的话,那就是从-8 ~ 7,也就是:1000 ~ 0111
如果需要表示8的话,那就要用5位数了。
 
为什么要这么设计负数?
主要是想负数和正数之间的相加,也能遵循正数之间的相加一样的效果。
比如:-1:1111, 4:0100, 他们相加,得到10011,保留4位,舍弃第1个1,得到0011,正好是3。 溢出的部分就舍弃。
所以计算机底层都是拿加法拼的。所以加法的运算特别快。受益最高。

 

关于负数

可以将int类型的最大数打印出来,Integer.MAX_VALUE,发现并不是32位都是1,而是第1位是0,后面全是1。
如果第一位是0,表示非负数,后面的数就是n * 2º + n * 2¹ + n * 2² + n * 2³....之和
如果第一位是1,表示负数,后面的数就是 数的之和 取反 + 1
 
如果一个数是负数,怎么知道表示负几呢?--将1后面的数,进行取反,再+1;
 
另外:-N = ~N + 1 , 不管正负都这么算
 
关于右移
num >> i
比如0110,右移1位,>>1,是什么意思?
>>1,表示带符号右移
0110,右移1位,得到0011,高位拿什么来补呢?
如果是>>, 拿符号位来补,因为0110的符号位是0,所以拿0来补,得到0011
如果是>>>,不管符号位是啥,一律拿0来补。
 
所以最高位是0的话,>>和>>>两个是等效的
 
比如:1110 右移1:
1110 >> 1, 得到1111
1110>>>1, 得到0111
 
比如:
public static void main(String[] args) {
    int a = Integer.MIN_VALUE;
    print(a);           //10000000000000000000000000000000
    print(a >> 1);      //11000000000000000000000000000000
    print(a >>> 1);     //01000000000000000000000000000000
}
右移实际上就是除以2的次方:
>> 1 :  相当于除以2¹
>> 2:   相当于除以2²
...
>> n:  相当于除以2的n次方
 
异或
0110 ^ 0101
计算:不同为1,相同为0
得到:0011
异或:想要好记,其实就是无进位相加就可以了。就是没有进位的加法。
同或:
计算:相同为1,不同为0
 
左移
左移没有带不带符号左移的,空出来位置一律拿0补。
<<
100,<< 1 得到1000,也就是4 -> 8,相当于乘以 2
101 << 1 , 得到1010, 也就是 2² + 2º -> 2³ + 2¹ ,也是相当于*2
01110 << 2,得到0111000,也就是2³+2²+2¹ -> 2⁵+2⁴+2³,相当于*2²
 
所以,左移n位,就等于乘以2ⁿ
 
 
例子0:打印一个int类型数的二进制数:
public static void print(int num) {
    for (int i = 31; i >= 0; i--) {
        System.out.print((num & (1 << i)) == 0 ? "0" : "1");
    }
    System.out.println();
}
 
 
例子1:二进制的一些运算
package com.cy.code;

/**
 * int类型的数为4字节,1字节=8bit,所以int类型的数为32位
 */
public class Code01_BinarySystem {

    public static void printBinary(int num) {
        for (int i = 31; i >= 0 ; i--) {
            System.out.print(((num >> i) & 1) == 0 ? "0" : "1");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        //非负数
        int a = 78;
        System.out.println(a);
        printBinary(a);
        System.out.println("=============a===============");

        //负数
        int b = -6;
        System.out.println(b);
        printBinary(b);
        System.out.println("=============b===============");

        //直接写二进制的形式定义变量
        int c = 0b1001110;
        System.out.println(c);
        printBinary(c);
        System.out.println("=============c===============");

        //直接写十六进制的形式定义变量
        // 0100 -> 4
        // 1110 -> e
        // 0x4e -> 01001110
        int d = 0x4e;
        System.out.println(d);
        printBinary(d);
        System.out.println("=============d===============");

        /**
         * ~、取反
         * 相反数都是 取反 + 1,不管正负都是这样算的
         */
        System.out.println(a);                    //78
        printBinary(a);                           //00000000000000000000000001001110
        printBinary(~a);                          //11111111111111111111111110110001
        int e = ~a + 1;     //等同于 -a           
        System.out.println(e);                    //-78
        printBinary(e);                           //11111111111111111111111110110010
        System.out.println("=============e===============");

        //ing long 的最小值,取相反数、绝对值,都是自己
        int f = Integer.MIN_VALUE;
        System.out.println(f);
        printBinary(f);
        System.out.println(-f);
        printBinary(-f);
        System.out.println(~f + 1);
        printBinary(~f + 1);
        System.out.println("=============f===============");

        /**
         * | & ^
         * | : 或,两位里面有1个是1,就能得到1
         * &: 与,两位里面都是1,才能得到1,否则得到0
         * ^:  异或,不同为1,相同为0
         */
        int g = 0b0001010;
        int h = 0b0001100;
        printBinary(g | h);    //00000000000000000000000000001110
        printBinary(g & h);    //00000000000000000000000000001000
        printBinary(g ^ h);    //00000000000000000000000000000110
        System.out.println("=============g、h===============");

        /**
         * <<
         */
        int i = 0b0011010;
        printBinary(i);
        printBinary(i << 1);
        printBinary(i << 2);
        printBinary(i << 3);
        System.out.println("=============i <<===============");

        /**
         * 非负数 >> >>> 效果一样
         */
        printBinary(i);
        printBinary(i >> 2);
        printBinary(i >>> 2);
        System.out.println("=============i >> >>>===============");

        /**
         * 负数 >> >>> 效果不一样
         */
        int j = 0b11110000000000000000000000000000;
        printBinary(j >> 2);
        printBinary(j >>> 2);
        System.out.println("=============j >> >>>===============");

        /**
         * 非负数 << 1,等同于乘以2
         * 非负数 << 2,等同于乘以4
         * 非负数 << 3,等同于乘以8
         * 非负数 << i,等同于乘以2的i次方
         * ...
         * 非负数 >> 1,等同于除以2
         * 非负数 >> 2,等同于除以4
         * 非负数 >> 3,等同于除以8
         * 非负数 >> i,等同于除以2的i次方
         * 只有非负数符合这个特征,负数不要用
         */
        int k = 10;
        System.out.println(k);
        System.out.println(k << 1);
        System.out.println(k << 2);
        System.out.println(k << 3);
        System.out.println(k >> 1);
        System.out.println(k >> 2);
        System.out.println(k >> 3);
        System.out.println("=============k===============");
    }
}
 
例子2:找到缺失的数字
package com.cy.code;

/**
 * 找到缺失的数字
 * 给定一个长度为n的数组arr,值一定都在[0, n]范围上,并且没有重复值
 * 那么[0, n]范围上一定缺了某个数字,找到这个缺失的数字
 * 测试连接:https://leetcode.cn/problems/missing-number/
 */
public class Code03_MissingNumber {

    public static void main(String[] args) {
        int[] a = new int[]{1, 3, 4, 5, 0};
        System.out.println(missingNumber(a));
    }

    public static int missingNumber(int[] nums) {
        int eorAll = 0, eorHas = 0;
        for (int i = 0; i < nums.length; i++) {
            eorAll ^= i;
            eorHas ^= nums[i];
        }

        eorAll ^= nums.length;
        return eorAll ^ eorHas;
    }
}
 
 
例子3:Brain Kernighan算法
package com.cy.code;

/**
 * Brain Kernighan算法
 * 提取出二进制里最右侧的1
 * 判断一个整数是不是2的幂
 * 测试连接:https://leetcode.cn/problems/power-of-two/
 */
public class Code04_PowerOfTwo {

    public static boolean isPowerOfTwo(int n) {
        return n > 0 && n == (n & -n);
    }
}
演算过程草图:
 
 
 
 
 
例子4:判断一个整数是不是3的幂
package com.cy.code;

/**
 * 判断一个整数是不是3的幂
 * 测试连接:https://leetcode.cn/problems/power-of-three/
 */
public class Code05_PowerOfThree {

    /**
     * 如果一个数字是3的某次幂,那么这个数一定只含有3这个质数因子
     * 1162261467是int型范围内,最大的3次幂,它是3的19次方
     * 这个1162261467只含有3这个质数因子,如果n也是只含有3这个质数因子,那么
     * 1162261467 % n == 0
     * 反之如果1162261467 % n != 0 说明n一定含有其他因子
     */
    public static boolean isPowerOfThree(int n) {
        return n > 0 && 1162261467 % n == 0;
    }
}

 

例子5:找到唯一出现奇数次的数
package com.cy.code;

/**
 * 找到唯一出现奇数次的数
 * 数组中1种数出现了奇数次,其他的数都出现了偶数次
 * 返回出现了奇数次的数
 * 测试连接:https://leetcode.cn/problems/single-number/
 */
public class Code06_SingleNumber {

    public static int singleNumber(int[] nums) {
        int eor = 0;
        for (int num : nums) {
            eor ^= num;
        }
        return eor;
    }
}
思路:异或运算,不管你以什么样的顺序去异或,得到的结果一定是1个。
演算草图:
 
 
 
例子6: 找到唯二出现奇数次的数
package com.cy.code;

/**
 * 找到唯二出现奇数次的数
 * 数组中有两种数出现了奇数次,其他的数都出现了偶数次
 * 返回这两种出现了奇数次的数
 * 测试连接:https://leetcode.cn/problems/single-number-iii/
 */
public class Code07_DoubleNumber {

    public static int[] doubleNumber(int[] nums) {
        int eor1 = 0;
        for (int num : nums) {
            //nums中有2种数a、b出现了奇数次,其他的数都出现了偶数次
            eor1 ^= num;
        }
        // eor1: a ^ b
        // 提取出二进制里最右侧的1
        // eor1:        0 1 1 0 1 0 0 0
        // rightOne:    0 0 0 0 1 0 0 0
        int rightOne = eor1 & (-eor1);
        int eor2 = 0;
        for (int num : nums) {
            if ((num & rightOne) == 0) {
                eor2 ^= num;
            }
        }
        return new int[] { eor2, eor1 ^ eor2};
    }
}
思路:
1.因为数组里只有a和b出现奇数次,所以数组里面的数全部连起来异或,得到的结果一定是eor1 = a ^ b
2.因为a ≠ b,所以 a ^ b一定不等于0
3.因为a ^ b一定不等于0, 则a ^ b用二进制表达的数一定有一个位置是1。
4.那么可以提取出二进制里最右侧的1这个数。rightOne
5.假设:
a:             10100111
b:             11001111
eor1:        01101000
rightOne: 00001000
6.由上可知,a和b在右边第4个位置不一样,a是0,b是1。那么可以根据右边第4个位置是0还是1,可以将数组分为两大阵营:第4个位置是0的,和第4个位置是1的,而且a和b一定可以分开。
7.可以将数组里面的数和rightOne进行&运算,就能分开第4个位置是0,或者不是0了。
8.因为a和b已经分开到不同的数组,那么只要对这个数组里的数全部^运算,就能得到a或者b了。
9.得到的数再和eor1进行^运算,就能得到另外一个数了。
 
演算草图:
 
 
 
 
 
 
--
 
 
posted on 2025-03-30 12:07  有点懒惰的大青年  阅读(2078)  评论(0)    收藏  举报