前言
位运算是计算机中常⽤的⼀种运算⽅式,它对⼆进制位进⾏操作。在一些复杂的问题当中使用位运算会有意想不到的结果。
一、位运算
首先我们先来里了解一下,位运算都有哪些:
- 按位与(&): 将两个操作数的对应位进行逻辑与操作,如果两个位都为1,则结果为1,否则为0。
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1
- 按位或(|): 将两个操作数的对应位进行逻辑或操作,如果两个位中至少有一个位为1,则结果为1。
0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1
- 按位异或(^): 将两个操作数的对应位进行逻辑异或操作,如果两个位相同则结果为0,不同则结果为1。
0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0
- 取反运算(~): 将操作数的每个位取反,即0变成1,1变成0。
~0 = 1
~1 = 0
- 左移运算(左移): 将操作数的二进制位向左移动指定的位数,右侧补0。
0010 << 1 = 0100
- 右移运算(右移): 将操作数的二进制位向右移动指定的位数,左侧使用原来的符号位填充。
1010 >> 1 = 1101
二、位运算例题
第一题
实现⼀个算法,确定⼀个字符串 s 的所有字符是否全都不同。
示例1:
输入:s=“leetcode”
输出:false
示例2:
输入:s=“abc”
输出:true
限制:
0 <= len(s) <= 100
s[i]仅包含⼩写字⺟
这里我们可以创建一个数组来标记每个字母,用1来表示已经出现过,0表示未出现,然后遍历字符串,如果重复出现,直接返回false,如果遍历字符串且每个字母仅出现一次,返回true。
当然我们可以使用一个变量的二进制位来标记一个字母是否出现过。例如,遍历字符数组时我们遇到了‘a’,自然而然可以想到,将二进制位的第一位变成1,那就表示,‘a’已经出现过了。那么怎么将变量的二进制位根据不同字母变成1?
我们将 astr[i]-‘a’ 表示不同字母在二进制位中,存放位置的号码,如果字母是a,那我们应该存在0处,b存在1处,以此类推,26个字母我们最多到25处。然后将1的二进制位移动相应的次数。刚好‘a’放在0处,就不用左移。最后赋值给u。
#include <stdio.h>
#include<stdbool.h>
bool isUnique(char* astr) {
int i = 0;
//定义变量⽤来标记
int s = 0;
while (astr[i]) {
//在这⾥定义u直接表⽰为左移后的结果
//astr[i]-'a'即为astr[i]在字⺟表中的顺序-1,即上⾯所说的w-1
int u = 1 << (int)(astr[i] - 'a');
//若未被标记则标记此位,即s加上u;
if ((u & s) == 0) s += u;
//若之前已经被标记,则返回false;
else return false;
i++;
}
//遍历结束不存在重复则返回true
return true;
}
int main() {
char astr[] = "abcdea";
printf("%d", isUnique(astr));
return 0;
}
第二题
两个整数之间的 汉明距离 指的是这两个数字对应⼆进制位不同的位置的数⽬。
给你两个整数 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 <= 2^31- 1
首先我们对x和y进行异或运算 (^),并赋给 r,由于异或运算的特性是两个位相同则结果为0,不同则结果为1。所以异或之后的二进制位为1的位置,该位置的x和y的二进制位的不同。
接着,我们要统计r的二进制位位1的数目,我们可以使用 r = r&(r-1)
举例, r 的二进制位为01110,则 r-1 为 01101,然后我们将 r 与 (r-1) 按位与运算,得到 01100,这时我们去除了 r 的二进制位最右边的1,同时定义一个变量,计数。
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int hammingDistance(int x, int y) {
//⾸先进⾏两数异或运算求得r
int r = x ^ y;
int n = 0;
//当r不为0时记录⼀次并且执⾏⼀次r&(r-1)操作
while (r) {
n++;
r = r & (r - 1);
}
//返回答案
return n;
}
int main() {
int x, y;
printf("x=");
scanf("%d",&x);
printf("y=");
scanf("%d",&y);
printf("汉明距离为%d", hammingDistance(x,y));
return 0;
}
第三题
给你⼀个整数数组 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 * 10^4
-231 <= nums[i] <= 231 - 1
除两个只出现⼀次的整数外,nums 中的其他数字都出现两次
异或运算具有以下几个特性:
- 交换律:a ^ b = b ^ a
- 结合律:(a ^ b) ^ c = a ^ (b ^ c)
- ⾃反性:a ^ a = 0
- 恒等式:a ^ 0 = a
- 消去律:a ^ b ^ a = b
利用这些特性可以解决这道题
若有数组nums[1,2,1,3,2,5],将每个元素异或 ret ^= nums[i] ,又因为a^a=0,实际上 ret=3^5
接着我们用 if(((ret >> i) & 1) == 1) ;pos=i;求出 ret 最右边二进制位为1的位置,因为我们知道ret二进制位为1的位置,其3与5该二进制位也一定不同,我们利用这个性质,分别异或二进制位(pos)为1,为0的元素,并放入创建的数组中
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int* singleNumber(int* nums, int numsSize, int* returnSize) {
//定义答案数组并为之分配内存
int* ans = calloc(2, sizeof(int));
int ret = 0;
int i = 0;
//计算数组中所有数异或起来的结果ret
for (i = 0; i < numsSize; i++) {
ret ^= nums[i];
}
//从低位往⾼位遍历计算ret的⼆进制中哪⼀位是1
int pos = 0;
for (i = 0; i < 32; i++) {
if (((ret >> i) & 1) == 1) {
pos = i;
break;
}
}
//3. 分组异或
for (i = 0; i < numsSize; i++) {
//若当前数pos位为1,作为其中⼀组对ans[0]进⾏异或操作
if (((nums[i] >> pos) & 1) == 1) {
ans[0] ^= nums[i];
}
//否则,作为另⼀组对ans[1]进⾏异或操作。
else {
ans[1] ^= nums[i];
}
}
//ans[1]另⼀种计算⽅法
//ans[1]=ret^ans[0];
//更新数组⻓度
*returnSize = 2;
//返回答案数组
return ans;
}
第四题
颠倒给定的 32 位⽆符号整数的⼆进制位。
示例 1:
输⼊:n = 00000010100101000001111010011100
输出:964176192 (00111001011110000010100101000000)
解释:输⼊的⼆进制串 00000010100101000001111010011100 表⽰⽆符号整数 43261596,
因此返回 964176192,其⼆进制表⽰形式为 00111001011110000010100101000000。
- 定义⼀个⽆符号整型变量 ans ,并初始化为 0。
- 从低位到⾼位遍历原数的每⼀位。对于原数的第 i 位,我们将其右移 i-1 位后和 1 进⾏与运算,得到它在第 i 位的值。
- 将得到的值左移 31-i+1 位,得到其实际应该放置的位置。
- 将原数的第 i 位的值赋值给 ans 的第 32-i+1 位。
• 需要注意的是,第⼀位的权值为 2^0,因此在计算时需要考虑实际左移和右移的位数。
uint32_t reverseBits(uint32_t n) {
int i = 0;
//定义⼀个⽆符号整型ans
uint32_t ans = 0;
//从低位往⾼位遍历,当i
for (i = 1; i <= 32; i++) {
//将原数的第i+1位赋值给ans的第32-(i+1)+1位
ans |= ((n >> (i - 1)) & 1) << (31 - i + 1);
}
//返回答案
return ans;
}
第五题
给你⼀个由不同字符组成的字符串 allowed 和⼀个字符串数组 words 。如果⼀个字符串的每⼀个字符都在 allowed 中,就称这个字符串是 ⼀致字符串 。
请你返回 words 数组中 ⼀致字符串 的数⽬。
示例:
输⼊:allowed = "ab , words = [“ad”,“bd”,“aaab”,“baa”,“badad”]
输出:2
解释:字符串 “aaab” 和 “baa” 都是⼀致字符串,因为它们只包含字符 ‘a’,‘b’。
将allowed的元素以1放在相应的二进制位中,mask |= (1<<(allowed[i]-‘a’)),mask的二进制位中的1就表示allowed所包含的元素
然后 if((mask & (1<<(words[i][j]-‘a’))) ==0) {flag=0;break;}(flag初始化为1)若进入到if语句中,就说明words的某个字符串中出现了allowed中不存在的字符,0赋值给flag;若对整个字符串遍历且没有进入if语句,说明这是个一致字符串,count+=flag。
int countConsistentStrings(char* allowed, char** words, int wordsSize) {
int i = 0;
//定义变量⽤来标记
int mask = 0;
//定义变量⽤来计数
int count = 0;
//遍历字符串allowed,将其中每个字符在字⺟表中的顺序标记为1
while (allowed[i]) {
//或运算后这⼀位⼀定为1
mask |= (1 << (allowed[i] - 'a'));
i++;
}
//遍历字符串数组words
for (i = 0; i < wordsSize; i++) {
int j = 0;
//定义变量⽤来标记计数是否加⼀
int flag = 1;
//遍历当前字符串的字符
while (words[i][j]) {
//若当前字符未被标记,则当前字符串中有字符未在字符串allowed中出现
if ((mask & (1 << (words[i][j] - 'a'))) == 0) {
//计数不可加⼀,更新flag
flag = 0;
//后⾯的字符是⽆⽤判断,直接跳出
break;
}
j++;
}
//更新计数
count += flag;
}
//返回计数
return count;
}
第六题
未知 整数数组 arr 由 n 个⾮负整数组成。
经编码后变为⻓度为 n - 1 的另⼀个整数数组 encoded ,其中 encoded[i] = arr[i] ^ arr[i + 1] 。
例如,arr = [1,0,2,1] 经编码后得到 encoded = [1,2,3] 。
给你编码后的数组 encoded 和原数组 arr 的第⼀个元素 first(arr[0])。
请解码返回原数组 arr 。可以证明答案存在并且是唯⼀的。
示例 1:
输⼊:encoded = [1,2,3], first = 1
输出:[1,0,2,1]
解释:若 arr = [1,0,2,1] ,那么 first = 1 且 encoded = [1 ^ 0, 0 ^ 2, 2 ^ 1] = [1,2,3]
int* decode(int* encoded, int encodedSize, int first, int* returnSize) {
//a^b = c
//c^a --> a^b^a = b
//定义arr数组并分配内存
int* arr = (int*)malloc((encodedSize + 1) * sizeof(int));
//为arr数组第⼀个元素赋值
arr[0] = first;
int i = 0;
//为arr数组元素依次赋值
for (i = 1; i < encodedSize + 1; i++) {
// arr[i] = encoded[i - 1] ^ arr[i - 1]
//arr[i-1]^arr[i] = encoded[i - 1] ^ arr[i - 1]^arr[i - 1]
//arr[i-1]^arr[i] = encoded[i - 1]
arr[i] = encoded[i - 1] ^ arr[i - 1];
}
//更新数组⻓度并返回数组
*returnSize = encodedSize + 1;
return arr;
}
第七题
编写⼀个函数,输⼊是⼀个⽆符号整数(以⼆进制串的形式),返回其⼆进制表达式中数字位数为 '1’的个数(也被称为汉明重量)。
示例 1:
输⼊:n = 11 (控制台输⼊ 00000000000000000000000000001011)
输出:3
解释:输⼊的⼆进制串 00000000000000000000000000001011 中,共有三位为 ‘1’。
int hammingWeight(uint32_t n) {
int i = 0;
int cnt = 0;
for (i = 0; i < 32; i++) {
if (((n >> i) & 1) == 1) {
cnt++;
}
}
return cnt;
}
如果你喜欢这篇文章,点赞👍+评论+关注⭐️哦!
欢迎大家提出疑问,以及不同的见解。