个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创位运算(2)_5道算法题入门位运算
收录于专栏【经典算法练习】
本专栏旨在分享学习算法的一点学习笔记,欢迎大家在评论区交流讨论💌
温馨提示:
本文的算法题需要一些位运算知识的基础,如果大家还不是很了解的话,可以先去看下面的博客:
位运算(1)_常见位运算总结-CSDN博客
1. 位1的个数
OJ链接: 位1的个数
题目描述:
编写一个函数,获取一个正整数的二进制形式并返回其二进制表达式中
设置位
的个数(也被称为汉明重量)。
示例 1:
输入:n = 11
输出:3
解释:输入的二进制串 1011 中,共有 3 个设置位。
示例 2:
输入:n = 128 输出:1 解释:输入的二进制串 10000000 中,共有 1 个设置位。
示例 3:
输入:n = 2147483645 输出:30 解释:输入的二进制串 1111111111111111111111111111101 中,共有 30 个设置位。
提示:
1 <= n <= 231 - 1
代码展示:
class Solution {
public:
int hammingWeight(int n) {
int ret = 0;
while(n)
{
n &= (n - 1);//消去最低位1
ret++;
}
return ret;
}
};
2. 比特位计数
OJ链接: 比特位计数
题目描述:
给你一个整数 n
,对于 0 <= i <= n
中的每个 i
,计算其二进制表示中 1
的个数 ,返回一个长度为 n + 1
的数组 ans
作为答案。
示例 1:
输入:n = 2 输出:[0,1,1] 解释: 0 --> 0 1 --> 1 2 --> 10
示例 2:
输入:n = 5 输出:[0,1,1,2,1,2] 解释: 0 --> 0 1 --> 1 2 --> 10 3 --> 11 4 --> 100 5 --> 101
提示:
0 <= n <= 105
进阶:
- 很容易就能实现时间复杂度为
O(n log n)
的解决方案,你可以在线性时间复杂度O(n)
内用一趟扫描解决此问题吗? - 你能不使用任何内置函数解决此问题吗?(如,C++ 中的
__builtin_popcount
)
解法一(内置函数):
class Solution {
int check(int n)
{
int count = 0;
for(int i = 0; i < 32; i++)
if(((n >> i) & 1) == 1) count++;
return count;
}
public:
vector<int> countBits(int n) {
vector<int> ans(n + 1);
for(int i = 0; i <= n; i++)
{
int ret = check(i);
ans[i] = ret;
}
return ans;
}
};
解法二(动态规划):
class Solution {
public:
vector<int> countBits(int n) {
vector<int> ans(n + 1);
for (int i = 1; i <= n; i++) {
// 使用 ans[i >> 1] 和 (i & 1) 来计算位数
ans[i] = ans[i >> 1] + (i & 1);
}
return ans;
}
};
我们可以通过以下关系推导出算法的正确性:
对于任何一个整数 i,可以将其表示为:
i = 2 * j(i 的最低位为 0)
i = 2 * j + 1(i 的最低位为 1)
这意味着:如果 i 是偶数(即最低位为 0),则 countBits(i) = countBits(i >> 1)。
如果 i 是奇数(即最低位为 1),则 countBits(i) = countBits(i >> 1) + 1。
3. 汉明距离
OJ链接: 汉明距离
题目描述:
两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。
给你两个整数 x
和 y
,计算并返回它们之间的汉明距离。
示例 1:
输入:x = 1, y = 4 输出:2 解释: 1 (0 0 0 1) 4 (0 1 0 0) ↑ ↑ 上面的箭头指出了对应二进制位不同的位置。
示例 2:
输入:x = 3, y = 1 输出:1
提示:
0 <= x, y <= 231 - 1
代码展示:
class Solution {
public:
int hammingDistance(int x, int y) {
int ret = 0, tmp = 0;
tmp = x ^ y;
while(tmp)
{
tmp &= (tmp - 1);
ret++;
}
return ret;
}
};
利用了异或不同位为1的特性 ,然后寻找tmp中1的个数
4. 只出现一次的数字
OJ链接: 只出现一次的数字
题目描述:
给你一个 非空 整数数组 nums
,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
示例 1 :
输入:nums = [2,2,1] 输出:1
示例 2 :
输入:nums = [4,1,2,1,2] 输出:4
示例 3 :
输入:nums = [1] 输出:1
提示:
1 <= nums.length <= 3 * 104
-3 * 104 <= nums[i] <= 3 * 104
- 除了某个元素只出现一次以外,其余每个元素均出现两次。
代码展示:
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ret = 0;
for(auto ch : nums)
ret ^= ch;
return ret;
}
};
5. 只出现一次的数字III
OJ链接:只出现一次的数字III
题目描述:
给你一个整数数组 nums
,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
你必须设计并实现线性时间复杂度的算法且仅使用常量额外空间来解决此问题。
示例 1:
输入:nums = [1,2,1,3,2,5] 输出:[3,5] 解释:[5, 3] 也是有效的答案。
示例 2:
输入:nums = [-1,0] 输出:[-1,0]
示例 3:
输入:nums = [0,1] 输出:[1,0]
提示:
2 <= nums.length <= 3 * 104
-231 <= nums[i] <= 231 - 1
- 除两个只出现一次的整数外,
nums
中的其他数字都出现两次
代码展示:
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
long long a = 0;
for(auto ch : nums)
a ^= ch;
a &= (-a);
int first = 0, second = 0;;
for(auto ch : nums)
{
if(ch & a) first ^= ch;
else second ^= ch;
}
return {first, second};
}
};
使用异或运算:
异或运算(^ )有几个重要的性质:
x^ x = 0:任何数与自身异或的结果为 0。
x ^ 0 = x:任何数与 0 异或的结果为其自身。
异或运算是可交换和结合的:即 x ^ y = y ^ x,(x ^ y) ^ z = x ^ (y ^ z)。
因此,通过对数组中所有数字进行异或运算,能够得到两个只出现一次的数字的异或结果。假设这两个数字分别为 x 和 y,最终的结果是 a = x ^ y。提取不同的位:
由于 x 和 y 是不同的数字,它们的异或结果 a 至少有一个位是 1。通过 a & (-a) 得到 a 最低位的 1。这个操作的本质是获取 a 的最后一个二进制位 1,标记出 x 和 y 在这一位上的差异。
- a 是 a 的补码表示,它的二进制形式中只有最低位的 1 保留为 1,其他位都为 0。因此,a & (-a) 只会保留 a 的最低位的 1。
分组数字:接下来,遍历原数组,根据当前数字在这个标记位上的值(即该位是否为 1)将数字分为两组:
第一组:包含所有在该位上为 1 的数字。
第二组:包含所有在该位上为 0 的数字。
然后,对这两组分别进行异或运算:
first 用于存储第一组的结果,second 用于存储第二组的结果。
返回结果:最后,返回 first 和 second,这就是两个只出现一次的数字。