题目[题目及部分题解来自LeeCode]
solution one
注意题目要求时间复杂度 O(N) ,空间复杂度 O(1) ,因此首先排除 暴力法 和 哈希表统计法 。但如果没有要求时间、空间复杂度,就可以用着两者方法,下面给出使用哈希表代码:class Solution {
public int[] singleNumbers(int[] nums) {
Map<Integer,Integer> set = new HashMap<Integer, Integer>();
boolean[] arr1 = new boolean[nums.length];
int[] arr = new int[2];
for(int i = 0; i < nums.length; ++i){
if( set.get(nums[i]) == null ) set.put(nums[i], i);
else{
arr1[set.get(nums[i])] = true;
arr1[i] = true;
}
}
for(int j = 0, k = 0; j < arr1.length; ++j){
if(arr1[j] == false){
arr[k] = nums[j];
++k;
}
}
return arr;
}
}
solution two
在了解第二种方法前,我先改下题目。如果将除两个数字以外改成一个数字以外,你会怎么做?如:设整型数组 nums 中出现一次的数字为 x ,出现两次的数字为 a, a, b, b,... x,即: nums = [a, a, b, b,... x]。think about it for one minute !注意,异或有个重要的性质,两个相同数字异或为 0,不同数字异或为1,即对于任意整数 a 有 a ⊕ a = 0 。因此,若将 nums 中所有数字执行异或运算,留下的结果则为 出现一次的数字 x ,即:
a ⊕ a ⊕ b ⊕ b ⊕ ··· ⊕ x
= 0 ⊕ 0 ⊕ ··· ⊕ x
= x
异或运算满足交换律 a ⊕ b = b ⊕ a ⊕ b = b ⊕ a ,即以上运算结果与 nums 的元素顺序无关。代码如下:
public int[] singleNumber(int[] nums) {
int x = 0;
for(int num : nums) // 1. 遍历 nums 执行异或运算
x ^= num;
return x; // 2. 返回出现一次的数字 x
}
而本体难点在于,数组nums有 两个 只出现一次的数字,因此无法通过异或直接得到这两个数字。
本体采用 分组异或 的方法:
- 首先用初始化为0的res对每个元素进行全员异或(0与任意非0数异或结果等于其本身),这样得到的结果就是两个不同的数 a 和 b 的异或结果。
- 题目中给定是两个不同的数 a 和 b ,则 a 和 b 在相同的二进制位必有一个1和一个0。(如果都相同,a 和 b就是同一个数了)
- 利用这个区别来对nums数组中的数进行分组,假设第二位a为1,b为0,则用2(2的二进制位中的第二位为1)来与nums数组中进行与运算(与运算相同为1,不同为0),与运算结果为1,则与a进行异或运算,结果为0则与b进行异或运算。这样就起到分组的效果。
class Solution {
public int[] singleNumbers(int[] nums) {
int group1 = 0, group2 = 0,res = 0, position = 1;
// 全员异或遍历,得到两个不重复的数字异或结果
for(int num : nums){
res ^= num;
}
// 找到两个数字所在位不同的位置
while( ( position & res) == 0 ){
position <<= 1;
}
// position位是0分成一组, position位是1分成一组
for(int num : nums){
if( (position & num) == 0 )
group1 ^= num;
else
group2 ^= num;
}
return new int[] {group1, group2};
}
}