位操作专项

113 阅读4分钟

两个int交换数字内容

题目

不实用临时变量的情况下, 完成两个int数字的数值交换

版本1 利用加法

    public static void main(String [] args) {
        int m = 5;
        int n = 12;
        //方法二:不定义临时变量,使用相加
        //优点:无需定义临时变量,节省内存。缺点:当两个数值较大时,容易丢失精度
        m = m + n; //12 + 5
        n = m - n; //12 + 5 - 5,是原来的m的值,赋给n
        m = m - n; //12 + 5 - 12,是原来n的值,赋给m
    }

版本2 利用异或

    public static void main(String [] args) {
        int m = 5;
        int n = 12;
        //方法三:大神专用,使用异或^
        //优点:不会丢失精度,不用定义变量,内存消耗最小,缺点:一般人不会想到这么写
        m = m ^ n;
        n = m ^ n;// m ^ n ^ n == m
        m = m ^ n;// m ^ n ^ m == n
    }

只出现一次的数字

题目

image.png

版本1 正确

    public int singleNumber(int[] nums) {


        // 寻找只出现了一次的数字
        // 同样的数字异或后变为0 例如3 ^ 3 = 0;
        // 利用一个数字和nums中所有数字异或, 这样每个位置要么异或0次, 要么异或2的倍数次, 要么异或2的倍数 + 1次
        // 最后剩下的就是只出现了一次的数字

        int one = 0;
        for (int i = 0; i < nums.length; i ++) {
            one ^= nums[i];
        }
        return one;
    }

只出现一次的数字2

题目

image.png

版本1 正确

    public int singleNumber(int[] nums) {

        // 一个数字出现一次, 其余数字出现三次
        // 异或三次的话, 还是那个数字本身, 无法直接使用
        // 考虑使用两个数字还存储, one用来表示第一次出现, 第二次出现的时候, 令two为相应数字, 然后one变成0
        // 第三次出现的时候, two还是two, one变成相应的数字
        // 当one和two都是相应数字的时候, 消除一次one和two
        // 同样的对于one来说每个位置会被处理0次, 3的倍数次, 或者3的倍数 + 1次

        int one = 0;
        int two = 0;
        int three = 0;

        for(int i = 0; i < nums.length; i ++) {
            // one出现过的, 才会累加到two上, 这里用或运算, 因为two只需要在第三次出现的时候才被消除
            two |= one & nums[i];
            // one每次是异或, 这样第一次就是数字, 第二次是0, 第三次又是该数字
            one ^= nums[i];

            // 当one和two某些位置上都是1的时候, 就代表出现了3次
            three = one & two;
            one &= ~three;
            two &= ~three;

        }

        return two;
    }

只出现一次的数字3

题目

image.png

版本1 正确

    public int[] singleNumber(int[] nums) {

        // 数组中存在两个只出现一次的数字, 并且这两个数字一定不相同, 假设为ab
        // 对所有数字异或完的结果就是a ^ b, 他们的结果中一定存在有某一位是1
        // 假设异或结果第3位为1, 那么a的第三位为0, b的第三位为1(当然反过来也行, 但是一定是不一样的)
        // 因此就可以根据第三位是否为1, 将nums数组分成两个部分, ab在不同的部分, 两部分分别做异或, 就能得到ab

        // alla ^ b的结果
        int all = 0;
        for (int i = 0; i < nums.length; i ++) {
            all ^= nums[i];
        }

        // 寻找all哪一位是1
        // 这里定为从低位开始寻找

        // div = 1就是最低位为1
        int div = 1;
        // 循环条件就是div1的那一位, all是不是也为1
        while ((div & all) == 0) {
            // 本身的<<符号是不会改变原始值的, 这里也可以写成div <<= 1;
            div = div << 1;
        }

        // 此时div的某一位就为1, 将nums分成两组
        int a = 0;
        int b = 0;
        for (int i = 0; i < nums.length; i ++) {
            if ((div & nums[i]) == 0) {
                a ^= nums[i];
            } else {
                b ^= nums[i];
            }
        }
        
        return new int[]{a, b};
    }

不用加号的加法

题目

image.png

版本1 错误

    public int add(int a, int b) {

        while (b != 0) {
            // 利用异或计算ab, 相同为0, 即不考虑进位的结果
            a = a ^ b;
            // 计算进位的结果, 相与然后左移一位
            b = a & b << 1;
            
            // 当没有进位的时候, 停止相加

        }

        return a;

    }

错误的原因

(1) a = a ^ b;的时候, a的值就已经改变了, 再计算b的时候肯定就不对了

版本2 错误

    public int add(int a, int b) {

        while (b != 0) {
            // 利用异或计算ab, 相同为0, 即不考虑进位的结果
            int temp = a ^ b;
            // 计算进位的结果, 相与然后左移一位
            b = a & b << 1;

            // 当没有进位的时候, 停止相加
            a = temp;
        }

        return a;

    }

错误的原因

(1) b = a & b << 1; 这里的a & b一定要打括号, 不然就先计算 b << 1了

版本3 正确

    public int add(int a, int b) {

        while (b != 0) {
            // 利用异或计算ab, 相同为0, 即不考虑进位的结果
            int temp = a ^ b;
            // 计算进位的结果, 相与然后左移一位
            b = (a & b) << 1;

            // 当没有进位的时候, 停止相加
            a = temp;
        }

        return a;

    }