目录

一、按位操作符是什么?

二、异或运算的性质

2.1 - 交换两个变量的值

2.2 - 只出现一次的数字

2.3 -  丢失的数字

三、n & (n - 1)

3.1 - 计算一个整数的补码中 1 的个数

3.2 - 2 的幂



一、按位操作符是什么?

按位操作符包括

&(按位与)、|(按位或)、^(按位异或)

  1. 按位操作符的两个操作数必须是整型
  2. 按位与:两个操作数对应的二进制位同为 1,才为 1,否则为 0。
  3. 按位或:两个操作数对应的二进制位同为 0,才为 0,否则为 1。
  4. 按位异或:两个操作数对应的二进制位相同为 0,相异为 1。

二、异或运算的性质

异或运算的性质

  1. 任意一个变量 x 与其自身进行异或运算,结果为 0,即 x ^ x = 0
  2. 任意一个变量 x 与 0 进行异或运算,结果不变,即 x ^ 0 = x
  3. 异或运算满足结合律,即 a ^ b ^ c = (a ^ b) ^ c = a ^ (b ^ c)
  4. 异或运算满足交换律,即 a ^ b = b ^ a
  5. 异或运算满足自反性,即 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。

按位异或运算 Java 按位异或运算符号_c语言

 

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。