C++算法之神奇的位运算

位运算

位运算是算法题里比较特殊的一种类型,它们利用二进制位运算的特性进行一些奇妙的优化
和计算。常用的位运算符号包括:“∧”按位异或、“&”按位与、“|”按位或、“∼”取反、“<<”
算术左移和“>>”算术右移。以下是一些常见的位运算特性,其中 0s 和 1s 分别表示只由 0 或 1
构成的二进制数字。
在这里插入图片描述

1.基础问题

给定两个十进制数字,求它们二进制表示的汉明距离(Hamming distance,即不同位的个数)。
在这里插入图片描述
题解:对两个数进行按位异或操作,统计有多少个1即可。

int hanmingDistance(int x,int y)
{
    int diff=x ^ y,ans=0;
    while(diff)
    {
        ans+=diff&1;
        diff>>=1;
    }
    return ans;
}

题目一:
给定一个十进制整数,输出它在二进制下的翻转结果
在这里插入图片描述
题解:使用算数左移和右移,可以轻易的实现二进制的翻转

uint32_t reverseBits(unit32_t n)
{
    unit32_t ans=0;
    for(int i=0;i<32;++i)
    {
        ans <<= 1;
        ans += n & 1;
        n >>= 1;
    }
    return ans;
}       

题目二:
给定一个整数数组,这个数组里只有一个数次出现了一次,其余数字出现了两次,求这个只出现一次的数字。
在这里插入图片描述
我们可以利用 x ∧ x = 0 和 x ∧ 0 = x 的特点,将数组内所有的数字进行按位异或。出现两次的所有数字按位异或的结果是 0,0 与出现一次的数字异或可以得到这个数字本身。

int singleNumber(vector<int>& nums)
{
    int ans=0;
    for(const int & num:nums)
    {
        ans ^= num;
    }
    return ans;
}

2.二进制特性

利用二进制的一些特性,我们可以把位运算使用到更多问题上。
例如,我们可以利用二进制和位运算输出一个数组的所有子集。假设我们有一个长度为 n 的
数组,我们可以生成长度为 n 的所有二进制,1 表示选取该数字,0 表示不选取。这样我们就获
得了 2n 个子集。
题目一:
给定一个整数,判断它是否是 4 的次方。
在这里插入图片描述
首先我们考虑一个数字是不是 2 的(整数)次方:如果一个数字 n 是 2 的整数次方,那么它的二进制一定是 0…010…0 这样的形式;考虑到 n − 1 的二进制是 0…001…1,这两个数求按位与的结果一定是 0。因此如果 n & (n - 1) 为 0,那么这个数是 2 的次方。如果这个数也是 4 的次方,那二进制表示中 1 的位置必须为奇数位。我们可以把 n 和二进制的 10101…101(即十进制下的 1431655765)做按位与,如果结果不为 0,那么说明这个数是 4 的次方。

bool isPowerOfFour(int n)
{
    return n > 0 && !(n & (n-1)) && (n & 1431655765);
}

题目二:
给定多个字母串,求其中任意两个字母串的长度乘积的最大值,且这两个字母串不能含有相同字母。
在这里插入图片描述
怎样快速判断两个字母串是否含有重复数字呢?可以为每个字母串建立一个长度为 26 的二进制数字,
每个位置表示是否存在该字母。如果两个字母串含有重复数字,那它们的二进制表示的按位与不为 0。同时,我们可以建立一个哈希表来存储字母串(在数组的位置)到二进制数字的映射关系,方便查找
调用。

int maxProduct(vector<string>& words)
{
    unordered_map<int,int>hash;
    int ans=0;
    for(const string & word : words)
    {
        int mask=0,size=word.size();
        for(const char & c : word)
        {
            mask |= 1 << (c - 'a');
        }
        hash[mask] = max(hash[mask],size);
        for(const auto& [h_mask,h_len]:hash)
        {
            if(!(mask & h_mask))
            {
                ans = max(ans,size * h_len);
            }
        }
    }
    return ans;
}

题目三:
给定一个非负整数 n,求从 0 到 n 的所有数字的二进制表达中,分别有多少个 1。

    输入是一个非负整数 n,输出是长度为 n + 1 的非负整数数组,每个位置 m 表示 m 的二进制里有多少个 1。

在这里插入图片描述
本题可以利用动态规划和位运算进行快速的求解。定义一个数组 dp,其中 dp[i] 表示数字 i的二进制含有 1 的个数。对于第 i 个数字,如果它二进制的最后一位为 1,那么它含有 1 的个数则为 dp[i-1] + 1;如果它二进制的最后一位为 0,那么它含有 1 的个数和其算术右移结果相同,即dp[i>>1]。

vector<int>countBits(int num)
{
    vector<int>dp(num+1,0);
    for(int i=1;i<=num;++i)
    {
        dp[i] = i & 1 ? dp[i-1] + 1: dp[i>>1];
        //等价于dp[i] = dp[i&(i-1)] + 1;   
    }
    return dp;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

明月醉窗台

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

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

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

打赏作者

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

抵扣说明:

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

余额充值