算法之对症下药(二)

位运算是一种偏向于计算机底层的操作,需要我们比较了解计算机底层是如何运行的,比如二进制、补码、按位与、按位或等知识,虽然理解计算机底层来思考问题会比较复杂,也比较反人类,但是位运算却可以高效地运算(很简单的道理,编译器不需要先去理解语句的语义再转成机器语言,可以直接进行二进制运算),所以在算法题目中适当地运用一些位运算可以帮助我们AC题目

计算机的编码机制

原码

原码就是将我们看到的数据转成二进制,最高位是符号位,1为负数,0为正数,例如8位 5 的原码表示为(0000 0101)2 ,而 -5 的8位原码是(1000 0101)2

反码

反码是从原码进行变换得到的,正数的反码还是它本身,而负数的反码除了符号位之外全部取反,例如8位 5 的反码表示为(0000 0101)2 ,而 -5 的8位反码是(1111 1010)2

补码

正数的补码依旧是它本身(即正数的原反补码都是一样的),而负数补码是在反码的基础上加1得到的,例如 8位 5 的补码是(0000 0101)2,而 -5 的8位补码是(1111 1011)2补码是计算机存储器上真正存储的编码类型,它巧妙地让计算机进行加减法运算时变得方便,例如5-5=5+(-5)=(0000 0101)2 + (1111 1011)2 = (1 0000 0000)2,去掉高位(因为我们的数只有8位),即可以得到正确答案,于是计算机可以用加法器来进行减法运算

位运算

我们最常用的位运算有与、或、异或、左移、右移等,不同语言实现的位运算也有些区别,主要区别在右移,有些语言实现的右移是补符号位,而有些是直接高位补0,Java即是高位补0的一门语言,下面以Java的位运算符来说明

按位与运算

按位与运算即是两个数每一位进行与运算,规则为:同1则结果为1,否则为0
故9&7=(0000 1001)2 & (0000 0111)2 = (0000 0001) 2= 1

按位或运算

按位或运算即是两个数每一位进行或运算,规则为:同0则结果为0,否则为1
故9|7 = (0000 1001)2 | (0000 0111)2 = (0000 1111)2 = 15

异或运算

异或的规则为:同为0或同为1则结果为0,否则为1
故9^7 = (0000 1001)2 ^ (0000 0111)2 = (0000 1110)2 = 14

左移右移运算

左(右)移运算即是将所有位左(右)移指定位,低(高)位补0
例如9<<2 = (0000 1001)2 << 2 = (0010 0100)2 = 36
9 >> 2 = (0000 1001)2 >> 2 = (0000 0010)2 = 2

如果你对二进制比较熟悉,那你应该发现左移其实就是乘2,而右移就是地板除2

运用位运算解决算法题

题目1:数值的整数次方

题目描述:给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。保证base和exponent不同时为0

分析:在这道题里,我们要考虑一个情况,就是当exponent为负数时,结果应该是1/base(-exponent)。这道题如果我们用累乘的方式完成这道题会超时,如果调用Math的pow倒是可行的,我们这里使用的是算平方的方式,就是通过log(exponent)次的计算平方来得到最终的结果。
我们可以清楚地知道,127 = 124 *123 = 1222 * 122 * 12,并且在二进制中,当一个数是偶数时,其最低位为0,否则为1。我们就利用这一点,当exponent最低位为1时,表示我们应该多乘一个当前的base,而base在exponent除以2时应该平方,因为a2b = ab * ab 。 或者你可以这样理解,当exponent的最低位为1时,需要乘一次base,否则可以跳过。

public class Solution {
    public double Power(double base, int exponent) {
        //指数为0或基数为1可以直接返回1,减少不必要的计算
        if(base == 1 || exponent == 0)
            return 1;
        //判断指数是否为负数
        boolean n = exponent < 0 ? true : false; 
        //如果指数为负数先算出分母的值
        exponent = n ? -exponent : exponent; 
        double res = 1;
        while(exponent > 0){
        	//判断最低位为0还是为1
            if((exponent & 1) == 1){ 
                //为1则乘1次base
                res *= base; 
            }
            //指数除以2,base算平方
            exponent = exponent >> 1;
            base *= base;
        }
        return n ? 1.0/res : res;
    }
}
题目2:二进制中1的个数

题目描述:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

分析:Java的int类型是32位的,直接遍历32个位上分别为0还是为1,为1则直接计数器加一的方式也很快,是常数时间复杂度;程序员是追求完美的,有一种新的方法可以达到,有几个1就循环几次,这就是位运算的魅力。有一个不得不提的巧妙是:一个数 n 与 (n-1)做按位与运算可以将最低一个1置0
例如(1111 0000)2&(1110 1111)2 = (1110 0000)2,于是我们就可以顺利完成我们的题目解答了

public class Solution {
    public int NumberOf1(int n) {
        int count = 0;
        //因为n可能为负数,所以注意这里不是大于0
        while(n != 0){
        	//消灭一个1,计数加一
            count++;
            n &= (n-1);
        }
        return count;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值