目录
一、按位操作符是什么?
二、异或运算的性质
2.1 - 交换两个变量的值
2.2 - 只出现一次的数字
2.3 - 丢失的数字
三、n & (n - 1)
3.1 - 计算一个整数的补码中 1 的个数
3.2 - 2 的幂
一、按位操作符是什么?
按位操作符包括:
&(按位与)、|(按位或)、^(按位异或)
- 按位操作符的两个操作数必须是整型。
- 按位与:两个操作数对应的二进制位同为 1,才为 1,否则为 0。
- 按位或:两个操作数对应的二进制位同为 0,才为 0,否则为 1。
- 按位异或:两个操作数对应的二进制位相同为 0,相异为 1。
二、异或运算的性质
异或运算的性质:
- 任意一个变量 x 与其自身进行异或运算,结果为 0,即 x ^ x = 0。
- 任意一个变量 x 与 0 进行异或运算,结果不变,即 x ^ 0 = x。
- 异或运算满足结合律,即 a ^ b ^ c = (a ^ b) ^ c = a ^ (b ^ c)。
- 异或运算满足交换律,即 a ^ b = b ^ a。
- 异或运算满足自反性,即 a ^ b ^ a = b(由异或运算以上的性质可推:a ^ b ^ a = (a ^ a) ^ b = 0 ^ b = b)。利用异或运算的自反性,可以把成对的变量消去,只留下单独的那个变量。
2.1 - 交换两个变量的值
法一(使用临时变量):
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
// 交换两个变量的值
int tmp = a;
a = b;
b = tmp;
// 输出两个变量的值
printf("a = %d, b = %d\n", a, b); // a = 20, b = 10
return 0;
}
法二(使用加减法):
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a = a + b;
b = a - b; // 和减去 b 得 a
a = a - b; // 和减去 a 得 b
printf("a = %d, b = %d\n", a, b); // a = 20, b = 10
return 0;
}
使用加减法交换两个变量的值,a 和 b 不能过大,否则可能会超过类型所允许的最大值。
法三(使用异或运算):
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a = a ^ b;
b = a ^ b; // a ^ b ^ b = a
a = a ^ b; // a ^ b ^ a = b
printf("a = %d, b = %d\n", a, b); // a = 20, b = 10
return 0;
}
使用按位异或交换两个变量的值,首先可读性差,其次效率也不如使用临时变量的方法,最后异或操作符只能用于整数,所以我们一般还是使用临时变量来交换两个变量的值。
2.2 - 只出现一次的数字
题目描述:给你一个非空整数数组 nums,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现一次的元素。
题目链接:136. 只出现一次的数字 - 力扣(Leetcode)。
#include <stdio.h>
int main()
{
int nums[] = { 4, 1, 2, 1, 2 }; // 示例
int sz = sizeof(nums) / sizeof(nums[0]);
int result = nums[0];
int i = 0;
for (i = 1; i < sz; i++)
{
result ^= nums[i];
}
printf("%d\n", result); // 4
return 0;
}
2.3 - 丢失的数字
题目描述:给定一个包含 [0, n] 中 n(n >= 1) 个数的数组 nums,找出 [0, n] 这个范围内没有出现在数组中的那个数。
题目链接:268. 丢失的数字 - 力扣(Leetcode)。
法一:
#include <stdio.h>
int main()
{
int n = 9; // [0, 9]
int nums[] = { 9, 6, 4, 2, 3, 5, 7, 0, 1 }; // 示例
int sum = n * (n + 1) / 2; // sum = 0 + 1 + ... + 9
int i = 0;
for (i = 0; i < n; i++)
{
sum -= nums[i];
}
printf("%d\n", sum); // 8
return 0;
}
法二:
#include <stdio.h>
int main()
{
int n = 9; // [0, 9]
int nums[] = { 9, 6, 4, 2, 3, 5, 7, 0, 1 }; // 示例
int result = nums[0];
int i = 0;
for (i = 1; i < n; i++)
{
result ^= nums[i];
}
for (i = 0; i <= n; i++)
{
result ^= i;
}
printf("%d\n", result); // 8
return 0;
}
三、n & (n - 1)
n & (n - 1) 是算法中常见的位操作,作用是消除 n 的二进制表示中的最后一个 1。
3.1 - 计算一个整数的补码中 1 的个数
法一:
int number_of_one(unsigned int n)
{
int count = 0;
while (n != 0) // 当 n 不等于 0 时,表示 n 的二进制表示中还有 1
{
if (n % 2)
count++;
n /= 2;
}
return count;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = number_of_one(n);
printf("%d\n", ret);
return 0;
}
n 的最后一位要么是 0,要么是 1;n 其余的每位则要么是 0,要么是 2^k(k >= 1),所以 n % 2 即判断 n 的最后一位是 0 还是 1,而 n /= 2 则是舍去 n 的最后一位。
法二:
#include <stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
int count = 0;
int i = 0;
for (i = 0; i < 32; i++)
{
// 判断 n 的每一位是否等于 1
if (n & (1 << i))
count++;
}
printf("%d\n", count);
return 0;
}
也可以使用 (n >> i) & 1 来判断 n 的每一位是否等于 1,其中 n >> i 没有副作用,即不会改变 n 的值。
法三:
#include <stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
int count = 0;
while (n != 0)
{
n &= (n - 1);
count++;
}
printf("%d\n", count);
return 0;
}
3.2 - 2 的幂
题目描述:给你一个整数 n,请你判断该整数是否是 2 的幂次方,如果是,返回 1;否则返回 0。如果存在一个整数 x,使得 n == 2^x,则认为 n 是 2 的幂次方。
题目链接:231. 2 的幂 - 力扣(Leetcode)。
int isPowerOfTwo(int n)
{
return n > 0 && (n & (n - 1)) == 0;
}
int main()
{
int n = 0;
scanf("%d", &n); // 1 --> 2^0
int ret = isPowerOfTwo(n);
if (ret)
{
printf("%d 是 2 的幂次方\n", n);
}
else
{
printf("%d 不是 2 的幂次方\n", n);
}
return 0;
}
一个整数如果是 2 的幂次方,首先这个整数是一个正整数,其次这个整数的二进制表示中只有一个 1,后面的其它位都是 0。