「算法」位运算的一些技巧


位运算是把数字用 二进制表示,对每一位上的 0 或 1 进行运算。位运算只有五种类型: 与(&)、或(|)、异或(^)、左移(<<)和右移(>>)

左移运算和右移运算

左移运算符 m<<n 表示把 m 左移 n 位,最左边的 n 位丢弃,同时在最右边补 n 个 0。
右移运算符 m>>n 表示把 m 右移 n 位,最右边的 n 位丢弃,如果 m 是无符号数,则用 0 填补最左边的 n 位;如果 m 是有符号数,则用符号位填补最左边的 n 位。

对于一个正整数,数字左移一位就是将数值乘 2,数字右移一位就是讲数值除以 2。位操作的效率要比运算符操作效率高,在实际编程过程中应尽量使用位移运算。

与、或、异或操作

一个数和另一个数进行两次异或操作后的结果仍为这个数,因此异或操作可以用来实现两个变量的置换。

x ^= y;
y ^= x;		// y == x;
x ^= y;		// x == y;

异或运算还有一个性质:任何一个数字异或自己都等于 0。对于数组中出现数字次数的题目,我们可以通过依次异或数组中每一个元素,得到最后不为 0 的数就是数组中出现次数为 1 的数。

使用 n & (n-1) 操作可以把最后一位的 1 变为 0。通过这一操作可以解决很多问题:

1. 判断 n 是否为 2 的幂

bool powerTow(int n)
{
	return n & (n-1) == 0;
}

2. 判断 n 是否为 4 的幂

如果一个数是 4 的幂,那么它必然是 2 的幂。因此我们可以先判断是否为 2 的幂。2 的幂中如果 1 的位置在奇数位上则是 4 的幂,所以我们跟 0x55555555 按位与。

bool powerFour(int n)
{
	if (n & (n-1) == 0)
		return (n & 0x55555555) != 0;		// 返回非 0 则是 4 的幂
	return false;
}

3. 求二进制中 1 的个数

题目来源:《剑指 offer》面试题 15

我们首先能想到的就是判断最右位是否为 1,如果是就讲计数变量加一,然后将该数右移一位,直到整个数变为 0。但是这样的思路有一个缺陷,当数值为负时,右移操作为保证该数仍为负,则会在符号位置 1,最后会陷入死循环。

解法一:为避免死循环我们不能右移输入的数。首先把 n 和 1 做与运算,判断 n 的最低位是不是为 1,然后把 1 左移一位得到 2,在和 n 做与运算就能判断 n 的次低位是否为 1。这样反复左移,每次都能判断 n 中的一位是否为 1.

int NumberOfOne(int n)
{
	int count = 0;
	unsigned int flag = 1;
	while (flag) {
		if (n & flag)	count++;
		flag = flag << 1;
	}
	return count;
}

解法二:利用 (n & n-1) 的性质来求二进制中 1 的个数

int NumberOfOne(int n)
{
	int count = 0;
	while (n) {
		++count;
		n = n & (n-1);
	}
	return count;
}

使用位操作的数值计算

1. 计算将一个数 m 改变为 n 所需要改变的二进制位数

只要计算两个数不同二进制位的个数即可,对于不同位我们很容易就能想到异或操作,异或操作就是将相同的位置 0,不同的位置 1。所以在异或操作之后只要统计 1 的个数就能得到两个数不同二进制位的个数。

void countDiffer(int m, int n)
{
	m = m ^ n;
	int count = 0;
	while (m) {
		++count;
		m = m & (m-1);
	}
	return count;
}

2. 不使用运算符实现加法运算**

int Add(int num1, int num2)
{
	int sum, carry;
	do {
		sum = num1 ^ num2;					// 异或结果为相加但不进位
    	carry = (num1 & num2) << 1;			// 只考虑进位的情况
    	num1 = sum;
    	num2 = carry;
	} while (num2 != 0);					// 重复上述过程直到进位为 0
	return sum;
}

3. 整数的平均数

直接使用 (x+y)/2 可能导致溢出,因为 x+y 可能会大于 INT_MAX,但是平均值是不会溢出的

int average(int x, int y)
{
	return (x & y) + ((x ^ y) >> 1);
}

4. 计算绝对值

int abs(int x)
{
	int y = x >> 31;
	return (x ^ y) - y;  // (x + y) ^ y
}

其他应用

1. 字符串转换成小写

使用位运算:

  • 大写字母和小写字母交换:c ^= 32;
  • 将字符转换为小写:c |= 32;
  • 将字符转换为大写:c &= -33;
class Solution {
public:
    string toLowerCase(string str) {
        string res = str;
        if (str.length() <= 0)
            return res;
        
        for (auto &c : res)
            c |= 32;
        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值