算法 —— 位运算

目录

位运算常用结论

位运算例题

位1的个数

比特位计算

 汉明距离

只出现一次的数字

判定字符是否唯一

丢失的数字

两整数之和

 消失的两个数字

进制转换


位运算常用结论

想详细了解位运算的内容可以阅读我的这篇博客:应该背下的位运算

以下我只介绍一些位运算的常用结论:

1、基础位运算:我们只需要记住以下口诀:

  1. &按位与:有0就是0
  2. | 按位或:有1就是1
  3. ^ 按位异或:相同为0,相异为1 / 无进制相加(两个1相加不向高处进位)

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

        ( n >> x ) & 1

3、将一个数n的二进制表示的第 x 位修改成1

        n | = ( 1 << x ) 

4、将一个数n的二进制表示的第 x 位修改成0

        n & = ( ~ ( 1 << x ) )

5、提取一个数n的二进制表示中最右侧的1

        n & -n

6、干掉一个数n的二进制表示中最右侧的1

        n & ( n - 1 )

7、异或运算律

  1. a ^ 0 = a
  2. a ^ a = 0
  3. a ^ b ^ c  = a ^ ( b ^ c )

位运算例题

位1的个数

利用上述第二个结论即可AC,代码如下:

class Solution {
public:
    int hammingWeight(int n) {
        int count = 0;
        while (n)
        {
            if (n & 1)
                count++;
            n >>= 1;
        }
        return count;
    }
};

比特位计算

 这里为了减小时间复杂度,我们利用动态规划进行完善,代码如下:

class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> ret(n + 1, 0);
        for (int i = 1; i <= n; i++)
        {
            if (i % 2) // 如果是奇数,那么就是前一个偶数的所有的1加1
                ret[i] = ret[i - 1] + 1;
            else // 如果是偶数,那么则等于它除二后的个数
                ret[i] = ret[i / 2];
        }
        return ret;
    }
};

为什么这里能用动态规划解决呢?好好思考一下,一个偶数变成一个奇数需要加一或者减一,在二进制上最直观的表现是最低位是否为1,如果为1那么就是奇数,如果为0那么就是偶数,这样就能够解释if语句里的内容了。

那么else语句中是什么意思呢?以6,3为例,6的二进制为110,3的二进制为11,二进制里的计算方式是一个二项式定理,这里不难发现,6是从3这样变化而来的:
2^{2}+2^{1} = \left ( 2^{1} + 2^{0} \right )*2


 汉明距离

两个数异或一下就能判别出所有1的二进制位,代码如下:

class Solution {
public:
    int hammingDistance(int x, int y) {
        int tmp = x ^ y, count = 0;
        while (tmp)
        {
            if (tmp & 1)
                count++;
            tmp >>= 1;
        }
        return count;
    }
};

只出现一次的数字

LeetCode —— 只出现一次的数字


判定字符是否唯一

大家先看一下我之前写的代码:

class Solution {
public:
    bool isUnique(string astr) {
        sort(astr.begin(), astr.end());
        for (int i = 1; i < astr.size(); i++)
            if (astr[i] == astr[i - 1])
                return false;
        return true;
    }
};

 直接排序加比较,时间复杂度稍微有些高,我们用位运算看能不能一次遍历就能找到正确答案:

class Solution {
public:
    bool isUnique(string astr) {
        // 比26字母表还大说明必定有重复
        if (astr.size() > 26)
            return false;
        int bitMap = 0;
        for (auto& ch : astr)
        {
            int tmp = ch - 'a';
            // 如果位图中的这一位存在了就说明已出现过
            if ((bitMap >> tmp) & 1)
                return false;
            // 第一次出现的加入到位图中
            bitMap |= (1 << tmp);
        }
        return true;
    }
};

这里我们使用一个int类型的变量来存值,可以存32个比特位,二进制位为0表示没出现,1为出现


丢失的数字

由于本题元素是一个等差数列,可以用求和公式直接计算出正常序列的和, 我们也可以用位运算解决这个问题,利用相同数字异或为0的结论即可AC,代码如下:

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int ret = 0;
        for (int i = 1; i <= nums.size(); i++)
            ret ^= (i ^ nums[i - 1]);
        return ret;
    }
};

两整数之和

这里又需要一个新的结论:进位,两个数进行按位与运算,如果有进位就左移一位

比如101和110相加,得到1011。最低位和第一位按位与之后变成0,第二位按位与后变成1,说明他是两个1相加的,意味着要进位,所以向左移动一位。

在文章最开始提到,异或运算是无进制相加,意味着它只能识别不要进位的那些二进制位,这时候我们把进位和异或运算结合在一起就可以实现不用加减运算符达到两整数之和的目的。

 以13+28为例,我们可以看到此方法可行,具体代码如下:

class Solution {
public:
    int getSum(int a, int b) {
        while (b)
        {
            int tmpa = a, tmpb = b;
            a = tmpa ^ tmpb; b = (tmpa & tmpb) << 1;
        }
        return a;
    }
};

 消失的两个数字

 本题用到了丢失的数字和只出现一次的数字里的知识,内容可看链接。

这里我们可以想象为正常序列和缺少的两个数字的缺失序列并在一起的大序列,其中有两个数字出现了一次,另外数字出现了两次,那么就可以进行分组。

我们把所有数字异或在一起,得到缺失两个数字异或的值,由于两个数字异或不可能等于0,所以他们最少有一个位不同,异或后的那个值1的最低位就是不同位,以这个为判定标准进行分组,代码如下:

class Solution {
public:
    vector<int> missingTwo(vector<int>& nums) {
        int tmp = 0; // tmp是缺失两个数异或得到的结果
        for (int i = 1; i <= nums.size() + 2; i++)
            tmp ^= i;
        for (auto& n : nums)
            tmp ^= n;
        vector<int>ret(2, 0); // 存放缺失的两个数
        int lowbit = tmp & -tmp; // 找到比特位为1的最低位
        for (auto& e : nums)// 分两组  
            ret[(e & lowbit) != 0] ^= e; // 注意加括号
        for (int i = 1; i <= nums.size() + 2; i++)
            ret[(i & lowbit) != 0] ^= i;
        return ret;
    }
};

进制转换

本题就不再是二进制位运算的解决方法了,很显然要解决这种题型首先要了解进制转换的规则:

n进制位转换成十进制位不用多说,一个求和累加公式即可,那么10进制转换为2进制怎么做呢?

我们利用不断除n得到余数,最后余数逆置就是转换后的结果,如图23从十进制转换为二进制,通过这个特性可以用代码来进行实现。

#include<bits/stdc++.h>
using namespace std;

int fn, ln; string num, ret;
long long tn, d = 1;

int main()
{
	cin >> fn >> num >> ln;
	for (int i = num.size() - 1; i >= 0; i--)
	{
		if (num[i] >= 'A' && num[i] <= 'F')
			tn += (num[i] - 'A' + 10) * d;
		else
			tn += (num[i] - '0') * d;
		d *= fn;
	}
	while (tn)
	{
		int tmp = tn % ln; tn /= ln;
		if (tmp >= 10)
			ret += (tmp - 10 + 'A');
		else
			ret += to_string(tmp);
	}
	reverse(ret.begin(), ret.end());
	cout << ret << endl;
	return 0;
}

如果出现了基数是负数的情况还能不断除法取余吗,答案是可以的,我们要怎么处理余数为负数的情况呢 ?这是除法运算法则:

被除数 = 商 * 除数 + 余数

当被除数或除数有一者为负数时就会出现余数为负数的情况,我们要避免这个情况发生,只需要将商+1,余数-除数即可,因为余数(绝对值)一定小于除数,所以这样就可以将余数转换为正数:

(商+1)* 除数 +(余数 - 除数)= 商 * 除数 + 除数 + 余数 - 除数 = 商 * 除数 + 余数 =被除数

 按照这个式子可以实现负进制的情况,代码实现如下:

#include<bits/stdc++.h>
using namespace std;
int num, r;
string ret;

int main()
{
	cin >> num >> r; int d = num;
	while (num)
	{
		int tmp = num % r;
		if (tmp < 0)
		{
			tmp -= r;
			num += r;
		}
		if (tmp >= 10)
			ret += (tmp - 10 + 'A');
		else
			ret += to_string(tmp);
		num /= r;
	}
	reverse(ret.begin(), ret.end());
	cout << d << '=' << ret << "(base" << r << ')' << endl;
	return 0;
}
  • 24
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值