1. 概述
在Java中,从指定范围内的数组(如:整数数组)中找出缺失的数字在各种场景下都非常有用,比如数据验证、确保完整性和识别数据集中存在的空缺。
在这个教程中,我们将学习如何使用多种方法从一个整数范围内([1-N])的数组中找到单个缺失的数字。
2. 理解场景
设想我们有一个包含[1-9]范围内的整数的数组numbers
:
int[] numbers = new int[] { 1, 4, 5, 2, 7, 8, 6, 9 };
目标是在数组[1-9]范围内找到缺失的那个数字。
为了概括问题陈述,我们可以计算数组的长度并设置上限N:
int N = numbers.length + 1;
接下来,我们会了解不同的方法来找到给定数组中范围[1-N]内缺失的数字。
3. 使用算术和
首先,我们用算术和的方法来找numbers
数组中的缺失数字。
首先,计算[1-N]范围内的等差数列预期总和以及数组的实际总和:
int expectedSum = (N * (N + 1)) / 2;
int actualSum = Arrays.stream(numbers).sum();
然后,从expectedSum
中减去actualSum
得到missingNumber
:
int missingNumber = expectedSum - actualSum;
最后,检查结果:
assertEquals(3, missingNumber);
结果正确!
4. 使用异或性质
另一种方法是利用异或操作(^)的两个有趣特性:
- X^X = 0: 当一个数与自身异或,结果为0。
- X^0 = X: 当一个数与0异或,结果还是原来的数。
首先,使用reduce
函数对闭区间[1-9]内的所有整数值进行异或操作:
int xorValue = IntStream.rangeClosed(1, N).reduce(0, (a, b) -> a ^ b);
我们使用了0和(a, b) -> a ^ b
作为reduce()
操作的身份和累积器。
接着,将异或操作扩展到numbers
数组的整数值:
xorValue = Arrays.stream(numbers).reduce(xorValue, (x, y) -> x ^ y);
由于除了缺失的数字外,数组中的每个数字都会出现两次,所以xorValue
将只包含数组[1-9]范围内numbers
中的缺失数字。
最后,确认我们的方法给出正确结果:
assertEquals(3, xorValue);
太好了!我们做得对。
5. 使用排序
我们的输入数组numbers
应包含范围[1-N]内连续的所有值,除了缺失的那个。因此,如果排序数组,会很容易发现缺失的数字,因为连续的数字之间会有空缺。
首先,对numbers
数组进行排序:
Arrays.sort(numbers);
接着,遍历数组,检查每个值是否是其索引加1:
int missingNumber = -1;
for (int index = 0; index < numbers.length; index++) {
if (numbers[index] != index + 1) {
missingNumber = index + 1;
break;
}
}
当条件不满足时,意味着数组中缺少值index + 1
。于是,设置missingNumber
并提前结束循环。
最后,检查我们是否得到了期望的结果:
assertEquals(3, missingNumber);
结果看起来正确。然而,在这个方法中,我们修改了原始输入数组。
6. 用布尔数组跟踪
在排序方法中,有两个主要缺点:
- 排序的开销
- 原始输入数组的修改
我们可以通过使用布尔数组来跟踪当前的数字来解决这些问题。
首先,定义一个大小为N的布尔数组present
:
boolean[] present = new boolean[N];
我们需要记住N被初始化为numbers.length + 1
。
接下来,遍历numbers
数组,在present
数组中标记每个数字的存在:
int missingNumber = -1;
Arrays.stream(numbers).forEach(number -> present[number - 1] = true);
然后,再次遍历present
数组,查找未被标记的数字:
for (int index = 0; index < present.length; index++) {
if (!present[index]) {
missingNumber = index + 1;
break;
}
}
最后,验证我们的方法,检查missingNumber
变量的值:
assertEquals(3, missingNumber);
完美!我们的方法有效。此外,我们使用了额外的N字节空间,因为每个布尔值在Java中占用1字节。
7. 使用位集优化空间复杂度
通过使用位集(Bitset),我们可以进一步优化空间复杂度。
BitSet bitSet = new BitSet(N);
这样初始化后,我们只需足够空间来表示N位。当N值很大时,这是一个相当可观的优化。
接下来,遍历numbers
数组,在位集bitset
中相应位置设置位:
for (int num : numbers) {
bitSet.set(num);
}
现在,我们可以通过检查未设置的位来找到缺失的数字:
int missingNumber = bitSet.nextClearBit(1);
最后,确认我们得到了正确的missingNumber
值:
assertEquals(3, missingNumber);
太棒了!看来我们成功了。
8. 总结
在这个教程中,我们学会了如何在数组中找到缺失的数字。我们探讨了多种方法来解决这个问题,例如算术和、异或操作、排序以及额外的数据结构,如位集和布尔数组。
如往常一样,本文的代码可以在GitHub上找到。