位运算总结(刷题)

1.二进制中1的个数

请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 ‘1’。
算法:
在分析这种算法之前,我们先来分析把-一个数减去1的情况。如果一个整数不等于0,那么该整数的二进制表示中至少有一位是1。先假设这个数的最右边一位是1,那么减去1时,最后一位变成0而其他所有位都保持不变。也就是最后一位相当于做了取反操作,由1变成了0。

接下来假设最后一位不是1而是0的情况。如果该整数的二进制表示中最右边的1位于第m位,那么减去1时,第m位由1变成0,而第m位之后的所有0都变成1,整数中第m位之前的所有位都保持不变。举个例子:一个二进制数1100, 它的第二位是从最右边数起的-一个1。减去1后,第二位变成0,它后面的两位0变成1,而前面的1保持不变,因此得到的结果是1011。

在前面两种情况中,我们发现把一个整数减去1,都是把最右边的1变成0。如果它的右边还有0,则所有的0都变成1,而它左边的所有位都保持不变。接下来我们把-一个整数和它减去1的结果做位与运算,相当于把它最右边的1变成0。还是以前面的1100为例,它减去1的结果是1011。我们再把1100和1011做位与运算,得到的结果是1000。 我们把1100最右边的1变成了0,结果刚好就是1000。
我们把上面的分析总结起来就是:把一个整数减去1,再和原整数做与运算,会把该整数最右边的1变成0。那么一个整数的二二进制表示中有多少个1,就可以进行多少次这样的操作。基于这种思路,我们可以写出新的代码:(参考剑指offer)

/**
     * 方法2:由于n和n-1作与&比较的时候,最右边的第一个1会变成0,而这个1后边的0会变成1,1会变成0
     * 把一个整数减去1,再和原整数做与运算,会把该整数最右边的1变成0。那么一个整数的二二进制表示中有多少个1,
     * 就可以进行多少次这样的操作。基于这种思路,我们可以写出新的代码:
     */
    public int hammingWeight2(int n) {
        int count = 0;
        while (n != 0) {
            count++;
            n = n & (n - 1);
        }
        return count;
    }

对于上边问题一般会有这么个存在缺陷的做法(不推荐的做法):

	int count = 0;
        while (n != 0) {
            if ((n & 1) == 1) {
                count++;
            }
            n = n >> 1;
        }
        return count;

2.求一个数是不是2的幂次方

	/**
     * 给定一个整数,编写一个函数来判断它是否是 2 的幂次方。
     * 输入: 16
     * 输出: true
     * 解释: 2^4 = 16
     * 算法:一个整数只要是2的幂次方,那么他的二进制表示中肯定就只有1个1,
     * 因此,对这个数的二进制表示减1的时候,原来1的位置会变成0,原来是0的地方会变成1
     * 将减1的数字与源数字进行与运算,如果源数字只有1个1的时候,那么与运算后,整个数会变成0
     */
    public boolean isPowerOfTwo(int n) {
        if (n == 0) {
            return false;
        }
        long x = (long)n;
        if ((x & (x - 1)) == 0) {
            return true;
        }
        return false;
    }

3.来个小结

把一个整数减去1之后再和原来的整数做位与运算,得到的结果相当于把整数的二进制表示中最右边的1变成0。很多二进制的问题都可以用这种思路解决。

3.1 与、或、异或、按位取反的运算规律总结

在这里插入图片描述
按位取反:~ :二进制位中的0变1,1变0

3.2 左移运算符"<<" 与右移运算符">>"

左移运算符m<<n表示把m左移n位。在左移n位的时候,最左边的n位将被丢弃,同时在最右边补上n个0。比如:

00001010<< 2 = 00101000
10001010<< 3 = 01010000

右移运算符m>>n表示把m右移n位。在右移n位的时候,最右边的n位将被丢弃。但右移时处理最左边位的情形要稍微复杂-一点。如果数字是一个无符号数值, 则用0填补最左边的n位:如果数字是一个有符号数值,则用数字的符号位填补最左边的n位。也就是说,如果数字原先是一个正数,则右移之后在最左边补n个0;如果数字原先是负数,则右移之后在最左边补n个1。下面是对两个8位有符号数进行右移的例子:

00001010>> 2 = 00000010
10001010>> 3 = 11110001

4.继续刷题:不用额外的变量交换两个整数的值(异或应用)

如何不用任何额外变量交换两个整数的值?
说明:
异或^,其实表示的是存储两个元素的不同属性,0 ^ 0 = 0;1 ^ 0 = 1; 0 ^ 1 = 1;1 ^ 1 = 0
明显地,可以看出,0表示的是相同的属性,1表示的是不同的属性
使用异或运算交换两个数字,第一次使用a记录两者的不同信息,第二次使用b记录与b相反的信息,第三次使用a记录与新b相反的信息,代码:

	/**
     * 编写一个函数,不用临时变量,直接交换numbers = [a, b]中a与b的值。
     * 算法:
     * 如果给定整数a和b,用以下三行代码即可交换a和b的值。
     * a=a^b;
     * b=a^ b;
     * a=a^ b;
     * 如何理解这三行代码的具体功能呢?首先要理解关于异或运算的特点:
     * 假设a异或 b的结果记为c,c就是a整数位信息和b整数位信息的所有不同信息。
     * 比如,a=4=100, b=3=011,a^b=c=111。
     * a异或c的结果就是b。比如a=4=100, c= =111, a^c=011=3=b。
     * b异或c的结果就是a。比如b=3=011, c= =111, b^c=100=4=a。
     */
    public int[] swapNumbers(int[] numbers) {
        numbers[0] = numbers[0] ^ numbers[1];
        numbers[1] = numbers[0] ^ numbers[1];
        numbers[0] = numbers[0] ^ numbers[1];
        return numbers;
    }

5. 不用任何比较判断找出两个数中较大的数

	public int flip(int n) {
        return n ^ 1;
    }

    public int sign(int n) {
        return flip((n >> 31) & 1);
    }

    public int getMax1(int a, int b) {
        int c = a - b;
        int scA = sign(c);
        int scB = flip(scA);
        return a * scA + b * scB;
    }

sign函数的功能是返回整数n的符号,正数和0返回1,负数则返回0。flip 函数的功能是如果n为1,返回0,如果n为0,返回1。所以,如果a-b的结果为0或正数,那么scA为1,scB为0;如果a-b的值为负数,那么scA为0,scB为1。scA和scB必有一个
为1,另一个必为0。所以returna* scA+b* scB;,就是根据a-b的值的状况,选择要么返回a,,要么返回b。
但方法一是有局限性的,那就是如果a-b的值出现溢出,返回结果就不正确。
第二种方法可以彻底解决溢出的问题,也就是如下代码中的getMax2方法。

	public int flip(int n) {
        return n ^ 1;
    }

    public int sign(int n) {
        return flip((n >> 31) & 1);
    }
    
	public int getMax2(int a, int b) {
        int c = a - b;
        int sa = sign(a);
        int sb = sign(b);
        int sc = sign(c);
        int difSab = sa ^ sb;
        int sameSab = flip(difSab);
        int returnA = difSab * sa + sameSab * sc;
        int returnB = flip(returnA);
        return a * returnA + b * returnB;
    }

借鉴左神的程序员面试指南:
在这里插入图片描述

6. 只用位运算不用算术运算实现整数的加减乘除运算

	public int add(int a, int b) {
        int sum,carry;
        while (b != 0) {
            sum = a ^ b;
            carry = (a & b) << 1;
            a = sum;
            b = carry;
        }
        return a;
    }

解释(剑指offer):
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值