算法学习——位运算

二进制中的位数:从最右边开始,第一个数记为第0位,第二个数记为第1位,以此类推。(为了方便与右移操作相匹配)

一、基础位运算

<<:左移

>>:右移

~:取反

&:按位与(有0就是0)

|:按位或(有1就是1)

^:按位异或(相同为0,相异为1,也就是无进位相加)

二、经典题目

(1)给一个数n,确定它二进制表示的第x位是0还是1

n>>x&1或者n>>x|0

(2)将一个数n的二进制表示的第x位修改为1

第x位修改为1:| 1

其余位保持不变:| 0

总体: n | (1<<x)

(3)将一个数n的二进制表示的第x位修改为0

第x位修改为0:& 0

其余位保持不变:& 1

总体: n & (~(1<<x))

(4)提取一个数n二进制表示中最右侧的1

n & -n

取负数操作相当于取反后加一:~n+1

目的:将原二进制数最右侧的1,左边区域全部变成相反(0和1互变),右边区域不变

(5)干掉一个数n二进制表示中最右侧的1

n & (n-1)

n-1作用:将最右侧的1和它的右侧全部取反。例如,最右侧位上的数为0,则需不断向左借位,以此达到效果

三、位图的思想

将一个int整形(32bit位)记成二进制,每一位都是0或1,就相当于一个数组可以用来记录信息

四、异或(^)运算律

1、a ^ 0 = a

2、a ^ a = 0(消消乐)

3、a ^ b ^ c = a ^ ( b ^ c )

五、例题

两整数之和

思路:

1、a ^ b表示a与b无进位相加的结果(如果该位上两数都是1则结果为0且不进位)

2、(a & b)<<1表示需要进位加1的位为1其余位为0:a & b表示a、b中只有该位上都是1结果才为1,否则结果为0,作用是将需要进位的当前位记为1,其余位记为0。由于进位要向左一位进,所以(a & b)后要<<1

3、a ^ b + (a & b)<<1就是a+b的结果,由于题目不允许直接相加,则继续将a ^ b视为新的a,(a & b)<<1视为新的b,继续用1、2的方法将新的a和b相加,不断重复此过程,直到新的b为0,则无需相加,新的a就是最终结果

class Solution {
    public int getSum(int a, int b) {
        while (b != 0) {
            int x = a ^ b;
            int carry = (a & b) << 1;
            a = x;
            b = carry;
        }
        return a;
    }
}

 

只出现一次的数字II

思路:

1、二进制的每一位不是0就是1,nums中所有数的和sum也同理。

2、假设只出现一次的数为a,nums除a外,第i位为0的数共有3x个,第i位为1的数共有3y个,这些数第i位之和为3的倍数。加上a后,sum%3的结果和a的第i位数字相同。

3、用此方法,计算出a的第i位上的数,以此类推计算出a的值

class Solution {
    public int singleNumber(int[] nums) {
        int ret = 0;
        for (int i = 0; i < 32; i++) {//i表示nums数组每个数需要右移的位数
            int sum = 0;
            for (int num : nums) {//nums数组每个数都右移i位
                if (((num >> i) & 1) == 1) {//如果x的第i位是1
                    sum++;//此时sum表示nums数组所有数第i位数字之和
                }
            }
            sum %= 3;//此时sum表示只出现一次的那个数第i位的数字
            if (sum == 1) {
                ret |= 1 << i;//将第i位修改为1
            }
        }
        return ret;
    }
}



消失的两个数字

思路:

1、假设nums数组长度为n-2,则包含了从1~n的所有整数(缺了两个数,假设为a,b)。所以,先将nums数组中所有数全部异或,继续将1~n的所有整数一起全部异或,根据异或定律,所得结果为a^b

2、由于a和b不相等,所以a^b肯定不为0,找出a^b最低位的那个1,将其记为diff位(假设a的diff位是0,b的diff位是1)。

由此,可以将nums中的数分为两类:

(1)diff位是0的数,统称为X1 

(2)diff位是1的数统称为X2

3、先将X1全部异或一遍,再将X1和a一起全部异或一遍,所得最终结果为a

先将X2全部异或一遍,再将X2和b一起全部异或一遍,所得最终结果为b

由此求出消失的数字a和b

class Solution {
    public int[] missingTwo(int[] nums) {
        //将所有数异或在一起,假设消失的两个数字为a,b
        int tmp = 0;
        for (int x : nums) {
            tmp ^= x;
        }
        for (int i = 1; i <= nums.length + 2; i++) {
            tmp ^= i;
        }
        //此时tmp=a^b

        //找出a,b中二进制最低位不同的那一位,也就是tmp中数字是1的那一位
        int diff = 0;
        while (true) {
            if (((tmp >> diff) & 1) == 1) {//如果tmp的第diff位是1则结束循环
                break;
            }else{
                diff++;
            }
        }

        //根据diff位的不同,将所有数划分为两类来异或
        //一类数第diff位是0,另一类数第diff位是1
        int a = 0, b = 0;
        for (int x : nums) {
            if (((x >> diff) & 1) == 1) {//第diff位是1的数和b异或
                b ^= x;
            } else {//第diff位是0的数和a异或
                a ^= x;
            }
        }

        for (int i = 1; i <= nums.length + 2; i++) {
            if (((i >> diff) & 1) == 1) {//第diff位是1的数和b异或
                b ^= i;
            } else {//第diff位是0的数和a异或
                a ^= i;
            }
        }
        return new int[]{a, b};
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值