【算法】位运算

【ps】本篇有 10 道 leetcode OJ。 

目录

一、算法简介

二、相关例题

1)位1的个数

.1- 题目解析

.2- 代码编写

2)比特位计数

.1- 题目解析

.2- 代码编写

3)汉明距离

.1- 题目解析

.2- 代码编写

4)只出现一次的数字

.1- 题目解析

.2- 代码编写

5)只出现一次的数字 III

.1- 题目解析

.2- 代码编写

6)判定字符是否唯一

.1- 题目解析

.2- 代码编写

7)丢失的数字

.1- 题目解析

.2- 代码编写

8)两整数之和

.1- 题目解析

.2- 代码编写

9)只出现一次的数字 II

.1- 题目解析

.2- 代码编写

10)消失的两个数字

.1- 题目解析

.2- 代码编写


 

一、算法简介

        程序中的所有数在计算机内存中都是以二进制的形式储存的,位运算就是直接对整数在内存中的二进制位进行操作,常见有逻辑与(&)、逻辑或(|)、逻辑异或(^)、按位取反(~)等。

【Tips】常见位运算方法总结

(1)区分位运算的优先级:别人工区分了,按题目要求能加括号就加括号!

(2)基础的与、或、异或

(3)位图相关

        确定 n 的二进制数的第 x 位是 0 还是 1:右移 x 后与 1

        将 n 的二进制数的第 x 位修改成 1:或上 1 的左移 x

        将 n 的二进制数的第 x 位修改成 0:与上取反的 1 的左移 x

        提取 n 的二进制数最右侧的 1:n 与 -n

        删除 n 的二进制数最右侧的 1:n 与 n - 1

        位图(哈希表)标识二进制数的比特位

(4)异或运算律

二、相关例题

1)位1的个数

191. 位1的个数

.1- 题目解析

        可以依此删除二进制数最右侧的 1,每删除一次都用一个计数器对其进行统计,直到二进制数中的 1 全部被删除。

.2- 代码编写

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

2)比特位计数

338. 比特位计数

.1- 题目解析

        这道题只是从上道题的“对一个二进制数求位 1 的个数”,变成了“对多个二进制数求位 1 的个数”。

.2- 代码编写

class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> v(n+1, 0);
        for(int i = 1; i <= n; ++i)
        {
            int cnt = 0, tmp = i;
            while(tmp)
            {
                tmp &= (tmp - 1);
                ++cnt;
            }
            v[i] = cnt;
        }
        return v;
    }
};

3)汉明距离

461. 汉明距离

.1- 题目解析

        将两个数异或后,两个数相同的比特位会变成 0 ,不同的比特位会变成 1,此时只需统计异或结果的 1 的个数即可。

.2- 代码编写

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

4)只出现一次的数字

136. 只出现一次的数字

.1- 题目解析

        把所有的数异或到一起,相同的数异或后结果为 0 ,剩余那个只出现了一次的数异或 0 还会等于它本身。

.2- 代码编写

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int val=0;
        for(auto e:nums)
        {
            val^=e;
        }
        return val;
    }
};

5)只出现一次的数字 III

260. 只出现一次的数字 III

.1- 题目解析

       假设数组中只有 2 和 4 只出现过一次。将数组中所有数字异或到一起,其他出现两次的数就抵消了,此时相当于 2 异或 4,即 0010 ^ 0100 =  0110.

        结合异或的结果和异或前运算数的比特位不难发现,只出现一次的数,其比特位在异或的结果中会被标识出来。由此,我们可以取异或结果最右侧的 1 是在第 cnt 个比特位上,将 1 左移 cnt 个比特位就能得到原来的数,此时再将 1 左移 cnt 个比特位的结果与数组异或一遍,以验证结果是否在数组中存在,若存在即返回该结果。

.2- 代码编写

class Solution {
public:
	vector<int> singleNumber(vector<int>& nums) {
        //1.将所有数异或到一起
		int sum = 0;
		for (const auto& e : nums)
		{
			sum ^= e;
		}
        //2.找到异或结果最右侧的 1 是第几个比特位
		int cnt = 0;
		for (int i = 0;i < 32;++i)
		{
			if (sum & (1 << i)) // 第几位是1,&的结果就是1
			{
				cnt = i;
			}
		}
        //3.在原数组中验证并返回结果
		vector<int> v = { 0,0 }; // C++11的初始化方式
		for (const auto& e : nums)
		{
			if (e & (1 << cnt)) // 第几位是1,&的结果就是1
			{
				v[0] ^= e; //记录其中一个只出现一次的数
			}
			else
			{
				v[1] ^= e; //另一个数只与其他出现过两次的数异或在一起,就得到了另一个数
			}
		}
		return v;
	}
};
//虽然吧,这道题用哈希的方式显然更方便,但本篇的专题是位运算呐!
class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        unordered_map<int, int> freq;
        for (auto e: nums) {
            ++freq[e];
        }
        vector<int> ans;
        for (const auto& [e, occ]: freq) {
            if (occ == 1) {
                ans.push_back(e);
            }
        }
        return ans;
    }
};

6)判定字符是否唯一

面试题 01.01. 判定字符是否唯一

.1- 题目解析

        不难想到用一个哈希表来统计每个字符出现的频次,但其实我们还可以用一个位图来充当哈希表。

        一个整型变量有 32 个比特位,每个比特位上的值要么为 0 ,要么为 1。而所谓位图,就是用一个 32 位的整型变量来对某些情况进行标识,其中,相应比特位上的值为 0 则表示不存在,为 1 则表示存在。

        此外,我们还可以对位图标识字符是否出现的这个过程,用鸽巢原理进行优化。

【Tips】 鸽巢原理

        若存在 n + 1 只鸽子,n 个巢穴,则至少有一个巢穴中的鸽子数量大于 1。

.2- 代码编写

class Solution {
public:
    bool isUnique(string astr) {
        if(astr.size()>26) //鸽巢原理优化
             return false;
        int bitMap=0;
        for(auto ch:astr)
        {
            int i=ch-'a';
            if((bitMap>>i)&1==1) //如果一个字符已经标识过了,就说明该字符是不唯一的
                return false;
            bitMap|=1<<i;        //标识尚未被标识的字符
        }
        return true; //位图能够顺利统计完所有字符,就说明字符串的所有字符是唯一的
    }
};

7)丢失的数字

268. 丢失的数字

.1- 题目解析

        直接将 [ 0,n ] 中的所有数,和数组中所有数异或在一起,就能找到那个不存在的数了。

.2- 代码编写

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int n=nums.size();
        int ret=0;
        //异或数组中的数
        for(auto e:nums)
        {
            ret^=e;
        }
        //异或[ 0,n ] 中的数
        for(int i=0;i<=n;i++)
        {
            ret^=i;
        }
        return ret;
    }
};

8)两整数之和

371. 两整数之和

.1- 题目解析

        如果是在笔试场上,可以不讲武德,直接 return a + b 即可。

        不过,这道题同样可以用异或运算律来解决。

        将两个二进制数与在一起的结果左移 1 位,会将它们相加时进位的比特位给标识出来。而两个数异或在一起,能够得到它们相加时未进位的部分,此时只需将左移 1 位的与结果和异或结果再异或一次,就能够得到两个数的和了。

         设 a = 13,b = 28,则 a + b = 41,我们只需对 a 和 b 重复上述过程,就能得到它们的和 41。

 

.2- 代码编写

class Solution {
public:
    int getSum(int a, int b) {
        //return a+b;
        while(b!=0)
        {
            int x=a^b;
            int carry=(a&b)<<1;
            a=x;
            b=carry;
        }
        return a;
    
    }
};

9)只出现一次的数字 II

137. 只出现一次的数字 II

 

.1- 题目解析

        若一个数只在数组中出现一次,其它数恰出现三次,则对于数组中所有的数,将它们的任意一个比特位统一地求和,都可能出现如下几种情况:

        如果将每一个比特位的这些情况 % 3,就能得到只出现一次的数的每一个比特位。

 

.2- 代码编写

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;//储存结果
        for(int i = 0; i < 32; ++i) // 依次修改ret的32个比特位(0改1)
            int sum = 0;
            for(auto e : nums)
                if(((e >> i) & 1) == 1) //计算nums中所有数第i个比特位之和
                     ++sum; 
            sum %= 3;
            if(sum == 1) 
                ret |= (1 << i); // 将ret的第i位改为1           
        return ret;
    }
};

10)消失的两个数字

面试题 17.19. 消失的两个数字

 

.1- 题目解析

        把这消失的两个数看成一个整体,然后将数组中的所有数与 [ 0,n ] 中的所有数异或在一起,就能得到这个整体。此时这个整体也是两个数异或的结果,这个结果中存在为 1 的比特位,就说明该比特位在两个数中必有一个为 1。

        由此,我们可以将数组中的数分为两类,一类是该比特位上为 1 的数,另一类是该比特位上不为 1 的数。然后再将这两类数分别异或在一起,就能分别得到消失的两个数了。

 

.2- 代码编写

class Solution {
public:
    vector<int> missingTwo(vector<int>& nums) {
        //1.将所有数异或在一起
        int tmp=0;
        for(auto e:nums)tmp^=e;
        for(int i=1;i<=nums.size()+2;i++)
            tmp^=i;
        //2.找出异或结果中为1的比特位
        int diff=0;
        while(1)
        {
            if(((tmp>>diff)&1)==1)
                break;
            diff++;
        }
        //3.将所有数分为两类,分别异或得出两个消失的数
        int a=0,b=0;
        for(auto e:nums)
        {
            if(((e>>diff)&1)==1) 
                a^=e;
            else 
                b^=e;
        }
        for(int i=1;i<=nums.size()+2;i++)
        {
            if(((i>>diff)&1)==1) 
                a^=i;
            else 
                b^=i;
        }
        return {a,b};
    }
};

  • 16
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值