在一组整数中,若有两个数字各自出现奇数次,其他数字均出现偶数次,如何高效地找出这两个数字?
一种常用且高效的方法是利用异或运算。
异或运算的性质:
- 自反性:
a ^ a = 0
,即相同的数字异或结果为0。 - 交换律和结合律:
a ^ b = b ^ a
,a ^ (b ^ c) = (a ^ b) ^ c
,即运算顺序和位置可以任意调整。 - 与0的运算:
a ^ 0 = a
,即任何数字与0异或结果为其本身。
基于这些性质,我们可以设计如下算法:
算法步骤:
-
对所有数字进行异或运算:遍历数组中的每个数字,将其与一个累积变量(初始值为0)进行异或操作。由于相同的数字出现偶数次,其异或结果为0,最终累积变量的值将是两个只出现奇数次的数字的异或结果。
-
从异或结果中提取信息:由于异或结果不为0,说明至少有一位二进制位为1。我们可以通过以下方式获取最右侧的1所在的位:
int rightmostOne = xorResult & (-xorResult);
这里,
-xorResult
是xorResult
的二进制补码,xorResult & (-xorResult)
的结果是xorResult
中最右侧的1所在的位。 -
根据该位将数组分为两组:遍历数组,将每个数字根据其在该位上的值(0或1)分配到两组中。
-
分别对两组进行异或运算:对每组中的数字分别进行异或操作,最终得到的结果即为两个只出现奇数次的数字。
Java实现示例:
public class FindOddNumbers {
public static void findOddNumbers(int[] arr) {
int xorResult = 0;
// 第一次遍历:对所有数字进行异或操作
for (int num : arr) {
xorResult ^= num;
}
// 获取 xorResult 最右侧的 1
int rightmostOne = xorResult & (-xorResult);
int num1 = 0, num2 = 0;
// 第二次遍历:根据 rightmostOne 将数组分为两组,分别对每组进行异或操作
for (int num : arr) {
if ((num & rightmostOne) != 0) {
num1 ^= num;
} else {
num2 ^= num;
}
}
System.out.println("出现奇数次的两个数字分别是: " + num1 + " 和 " + num2);
}
public static void main(String[] args) {
int[] arr = {1, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6, 7, 8, 8};
findOddNumbers(arr);
}
}
输出:
出现奇数次的两个数字分别是: 4 和 7
时间和空间复杂度:
- 时间复杂度:O(n),其中n是数组的长度。我们只需要遍历数组两次。
- 空间复杂度:O(1),只使用了常数空间。
通过上述方法,我们可以在O(n)的时间复杂度和O(1)的空间复杂度下,找出数组中两个只出现奇数次的数字。