算法多解——位运算经典例题详解合集篇

JZ15 二进制中1的个数

题面

思路1

n与n-1与运算,比如136=10001000,135=10000111

两数相与之后就减少了一个1,把遇到的1计数

class Solution {
public:
     int  NumberOf1(int n) {
         int cnt=0;
         while(n) {
             n = n&(n-1);
             cnt++;
         }
         return cnt;
     }
};

思路2

和1按位与

class Solution {
public:
     int  NumberOf1(int n) {
         int cnt=0, k=1;
         while(k) {
             if(n&k) {
                cnt++;
             }
             k <<= 1; //左移
         }
         return cnt;
     }
};

JZ65 不用加减乘除做加法

题面

思路1

  • 非递归方案
  • 两个数字相加的时候,我们先将两个数字视为二进制
  • 与运算产生进位的方案,之后左移1位就是每轮需要进位的方案
  • 或运算可以产生非进位的加和结果,因此每轮都要加和一次两个数字,视是否有进位来决定是否要下一轮继续循环

分布图解

复杂度分析1

  • 时间复杂度:O(log(max(num1,num2))),考虑二进制之后在原数字的基础上求了对数,循环的次数也和二进制位数相关
  • 空间复杂度:O(1),没有额外引入空间

代码1

class Solution {
public:
    int Add(int num1, int num2) {
        int sum = num1; //和
        int add = num2; //进位值
        while(add != 0) { //进位为0时,退出循环
            int temp = sum^add; //不考虑进位的和(新设变量延长赋值,防止对下面的add产生影响)
            add = (sum & add) << 1; //进位值
            sum = temp;
        }
        return sum; //没有进位时,返回总和
    }
};

思路2

  • 递归方案(很直接的思路)
  • 以num2承接是否有进位的工作,num1作为加和的结果
  • 首先判断num2是否有进位
    • 如果有进位则递归调用函数,并将num1更新为或运算的结果,num2更新为与运算左移一位的结果

    • 如果无进位则返回num1,因为num1一直在记录加和结果

复杂度分析2

  • 时间复杂度:O(log(max(num1,num2))),考虑二进制之后在原数字的基础上求了对数,循环的次数也和二进制位数相关
  • 空间复杂度:O(log(max(num1,num2))),递归栈的深度

代码2

class Solution {
public:
    int Add(int num1, int num2) {
        // 递归求和,也是判断是否有进位值,此时进位值用num2来表示,每轮的结果都存储在num1中
        return num2 ? Add(num1 ^ num2, (num1 & num2) << 1) : num1;
    }
};

JZ64 求1+2+3+...+n

题面

思路1 

  • 不能循环,就递归实现连加(到0时停止递归);
  • 不能使用选择/判断语句,就用与运算的短路操作:函数中,与运算成立才继续。否则函数终止返回false

复杂度分析1

  • 时间复杂度:O(n),一共递归n次
  • 空间复杂度:O(n),递归栈深度为n

代码1

class Solution {
public:
    int Sum_Solution(int n) {
        n && (n += Sum_Solution(n-1)); //与运算代为判断n是否为整数
        return n;
    }
};

思路2

  • 计算内存:通过公式和二维数组的空间大小结合起来
  • 等量关系:1+2+3+...+n = (n+1)∗n/2 = sizeof(a[n][n+1])/2 = sizeof(a)>>1
  • 其中数组a我们可以设置为bool型,只占一个空间,因此二维数组所占空间的一半就是我们要求的值。

复杂度分析2

  • 时间复杂度:O(1),直接计算,常数时间
  • 空间复杂度:O(n^2),辅助数组a的空间大小

代码2

class Solution {
public:
    int Sum_Solution(int n) {
        bool a[n][n+1];
        return sizeof(a)>>1;
    }
};

思路3

快速乘法

计算n(n+1)>>1n(n+1)>>1n(n+1)>>1即可。乘法不允许被使用,我们可以用快速乘法的加法来代替,快速乘法如下:

图解3

复杂度分析3

  • 时间复杂度:O(1),常数时间
  • 空间复杂度:O(1),常数空间

代码3

换成加法运算以后的代码为:

int fast(int x, int y){ //快速乘法
        int res = 0;
        while(y){
            if(y & 1){
                res += x; 
            }
            y = y >> 1;
            x = x << 1;
        }
        return res;
    }

这个算法中就全是加法和移位算法,但是还有循环和判断,判断我们可以用与运算解决,循环其实就是数字的位数,我们这里数字不会超过14位,因此我们将循环拆开直接写14次。

class Solution {
public:
    int res = 0;
    int Sum_Solution(int n) {
        int res = 0;
        int A = n, B = n + 1;
        //14次快速乘法
        (B & 1) && (res += A);
        A <<= 1;
        B >>= 1;
        (B & 1) && (res += A);
        A <<= 1;
        B >>= 1;
        (B & 1) && (res += A);
        A <<= 1;
        B >>= 1;
        (B & 1) && (res += A);
        A <<= 1;
        B >>= 1;
        (B & 1) && (res += A);
        A <<= 1;
        B >>= 1;
        (B & 1) && (res += A);
        A <<= 1;
        B >>= 1;
        (B & 1) && (res += A);
        A <<= 1;
        B >>= 1;
        (B & 1) && (res += A);
        A <<= 1;
        B >>= 1;
        (B & 1) && (res += A);
        A <<= 1;
        B >>= 1;
        (B & 1) && (res += A);
        A <<= 1;
        B >>= 1;
        (B & 1) && (res += A);
        A <<= 1;
        B >>= 1;
        (B & 1) && (res += A);
        A <<= 1;
        B >>= 1;
        (B & 1) && (res += A);
        A <<= 1;
        B >>= 1;
        (B & 1) && (res += A);
        A <<= 1;
        B >>= 1;
        return res >> 1; //除2
    }
};

JZ16 数值的整数次方

题面

思路

  • 快速幂思路就是增大底数减少循环次数。
  • 下面用^表示指数运算,讨论a^b非负指数情况:
  • 当b为偶数时,a^b = (a^2)^(b/2)
  • 当b为奇数时,a^b = a(a^2)^(b/2)
  • 程序实现时,奇偶判断可以用位与运算,除2可以用位移运算,负指数转化成正指数。 如果用递归实现看起来会更直观,下面用循环实现。

代码

class Solution {
public:
    double Power(double base, int exponent) {
		int tag = 0;
		if (exponent < 1) {
			exponent *= -1;
			tag = 1;
		}
		
		double ret = 1;
		while (exponent) {
			if (exponent & 1) ret *= base;
			base *= base;
			exponent >>= 1;
		}
		
		if (tag) ret = 1 / ret;
		return ret;		
    }
};

待添加...

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

米莱虾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值