数据结构与算法学习笔记

位运算



前言

位运算是算法题里比较特殊的一种类型,它们利用二进制位运算的特性进行一些奇妙的优化和计算。


一、算法概念

位运算总共分为两大类,逻辑位运算符和位移运算符。常用的位运算符号包括:“∧”按位异或、“&”按位与、“|”按位或、“∼”取反、“<<”算术左移和“>>”算术右移。
“∧”按位异或运算,首先将十进制的数换算成为二进制数,然后从低到高按位运算,对于每个位而言,只有当两个位不同的时候结果才为1,否则为0,位运算完成后,再把二进制转换为十进制得到运算结果。“∧”的性质,任何数和自己进行“∧”操作为0,任何数和0进行“∧”操作为它本身。
“<<”算术左移,是一种二元运算符,X<<y表示X左移y位,就是将X转换为二进制,然后将所有位左移y位,末尾y位补0。
“>>”算术右移,是一种二元运算符,X>>y表示X右移y位,与左移不同的是,如果X为非负数的高位补0,X为负数则在高位补1,因此右移操作可以看成对X除2并向下取整。

二、位运算基础问题分析和解答

1.位1的个数

问题描述

编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。

输入输出样例

输入:00000000000000000000000000001011
输出:3

解题思路与代码

对于二进制数10000减1为01111,对这两个数求&结果为0,因此可以用这种方式把每一位上的1都求出来。

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

2.4的幂

问题描述

给定一个整数,写一个函数来判断它是否是 4 的幂次方。如果是,返回 true ;否则,返回 false 。
整数 n 是 4 的幂次方需满足:存在整数 x 使得 n == 4^x

输入输出样例

输入:n = 16
输出:true

解题思路与代码

此题要考虑4 的幂次方数的特征,首先4 的幂次方数一定为2的幂次方数,其次4 的幂次方数减去1一定为3的倍数。对于2的幂次方数(比如:二进制数10000),其一定大于0,并且和它减一后的数做&运算结果一定为0。

class Solution {
public:
    bool isPowerOfFour(int n) {
        return n>0&&(n&(n-1))==0&&(n-1)%3==0;
    }
};

3.交换数字

问题描述

编写一个函数,不用临时变量,直接交换numbers = [a, b]ab的值。

输入输出样例

输入: numbers = [1,2]
输出: [2,1]

解题思路与代码

使用以下三步可以将a和b交换:
(1)将a^b赋值给a
(2)将a^b赋值给b,将(1)带入得b=a ^ b ^ b,又b ^ b=0,所以b=a ^ 0=a
(3)将a ^ b赋值给a,将(2)带入得a=a ^ b=a ^ a ^ b=0^b=b

class Solution {
public:
    vector<int> swapNumbers(vector<int>& numbers) {
        numbers[0]=numbers[0]^numbers[1];
        numbers[1]=numbers[0]^numbers[1];
        numbers[0]=numbers[0]^numbers[1];
        return numbers;
    }
};

4.只出现一次的数字

问题描述

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

输入输出样例

输入: [2,2,1]
输出: 1

解题思路与代码

可以考虑使用a ^ a=0和a^ 0=a这两个性质解题,对于数组中的每个数依次进行^操作,根据交换律的原则,最后会剩下只出现一次的那个数。

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

5.汉明距离

问题描述

两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。给你两个整数 xy,计算并返回它们之间的汉明距离。

输入输出样例

输入:x = 1, y = 4
输出:2

解题思路与代码

只需要考虑x^y之后的结果中有多少个1出现就行。对于统计1出现次数的问题又可以参考例题1的方法,这里就不再赘述。

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

6.交替位二进制数

问题描述

给定一个正整数,检查它的二进制表示是否总是 0、1 交替出现:换句话说,就是二进制表示中相邻两位的数字永不相同。

输入输出样例

输入:n = 5
输出:true
解释:5 的二进制表示是:101

解题思路与代码

可以反过来考虑,就是检测二进制数中是否有00或者11的情况出现,00对应的十进制数为0,11对应的十进制数为3。因此可以考虑使用0和3以及&运算来搜索整个二进制序列。

class Solution {
public:
    bool hasAlternatingBits(int n) {
        while(n)
        {
            if((n&3)==3||(n&3)==0)
            return false;
            n=n>>1; //n>>=1;
        }
        return true;
    }
};

7.两整数之和

问题描述

给你两个整数 ab不使用 运算符 +- ​​​​​​​,计算并返回两整数之和。

输入输出样例

输入:a = 1, b = 2
输出:3

解题思路与代码

首先,a ^ b可以看成是一种不进位的+操作,然后对a&b左移一位又可以得到进位的那个数,因此只要将a^b+(a&b)<<1就可以得到a+b,但是因为不能用+,因此可以考虑使用递归,而递归最后结束的条件是b==0。

class Solution {
public:
    int getSum(int a, int b) {
        return b==0?a:getSum(a^b,(unsigned int)(a&b)<<1);
    }
};

8.颠倒二进制位

问题描述

给定一个十进制整数,输出它在二进制下的翻转结果。

输入输出样例

输入和输出都是十进制整数。
输入: 43261596 (00000010100101000001111010011100)
输出: 964176192 (00111001011110000010100101000000)

解题思路与代码

使用算术左移和右移,可以很轻易地实现二进制的翻转。

class Solution {
public:
    uint32_t reverseBits(uint32_t n) {
        uint32_t ans = 0;
        for (int i = 0; i < 32; ++i) {
        //使用01模板获取n的每一位,push进ans中
        ans <<= 1;
        ans += n & 1; 
        n >>= 1;
        }
        return ans;
    }
};

9.最大单词长度乘积

问题描述

给你一个字符串数组 words ,找出并返回 length(words[i])*length(words[j]) 的最大值,并且这两个单词不含有公共字母。如果不存在这样的两个单词,返回 0 。

输入输出样例

输入:words = [“abcw”,“baz”,“foo”,“bar”,“xtfn”,“abcdef”]
输出:16
解释:这两个单词为 “abcw”, “xtfn”。

解题思路与代码

怎样快速判断两个字母串是否含有重复数字呢?可以为每个字母串建立一个长度为 26 的二进制数字,每个位置表示是否存在该字母。如果两个字母串含有重复数字,那它们的二进制表示的按位与不为 0。同时,我们可以建立一个哈希表来存储字母串(在数组的位置)到二进制数字的映射关系,方便查找调用。

class Solution {
public:
    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;
    }
};

总结

以上就是《位运算》的全部内容,文章会持续更新,如果文章中有写的不对的地方,希望大家可以在评论区进行批评和指正,大家一起交流,共同进步!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

猫大的救赎计划

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

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

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

打赏作者

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

抵扣说明:

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

余额充值