给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。
示例 :
输入: [1,2,1,3,2,5]
输出: [3,5]
注意:
结果输出的顺序并不重要,对于上面的例子, [5, 3] 也是正确答案。 你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?
第一种解法:Map暴力解法
public int[] singleNumber(int[] nums) {
Map<Integer, Integer> map = new HashMap();
for(int num:nums){
if(!map.containsKey(num)){
map.put(num,1);
}else{
map.put(num,2);
}
}
ArrayList<Integer> list =new ArrayList();
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
if(entry.getValue()==1){
list.add(entry.getKey());
}
}
int[] array=new int[list.size()];
for(int i=0;i<list.size();i++){
array[i]=list.get(i);
}
return array;
}
成绩就只能呵呵了

优化一下,具体说只有两个元素只出现一次,没有仔细看到。
public int[] singleNumber(int[] nums) {
Map<Integer, Integer> map = new HashMap();
for(int num:nums){
if(!map.containsKey(num)){
map.put(num,1);
}else{
map.put(num,2);
}
}
int[] array=new int[2];
int j =0;
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
if(entry.getValue()==1){
array[j] = entry.getKey();
j++;
}
}
return array;
}

第二种解法:位运算
题目标签提示到位运算,查了一些资料
异或规则
异或的运算方法是一个二进制运算:
1^1=0
0^0=0
1^0=1
0^1=1
两者相等为0,不等为1.
n^0=n n^n=0,即任何数与0进行异或,为它本身,两个相同的数进行异或运算,会得到0
例如:
1. a ^ b = b ^ a
2. a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c;
3. d = a ^ b ^ c 可以推出 a = d ^ b ^ c.
4. a ^ b ^ a = b.
从n^0=n n^n=0得出 a ^ b ^ a = b。知道相同数字异或等于0,然后再跟其他数字异或还是不变。 而且看到一些提示“全部异” 思路就是先把所有数都异或,得出最后值就是这两个唯一值的异或,然后通过Map反显求另外一半的逻辑。 这个时候利用例子3原理
public int[] singleNumber(int[] nums) {
Map<Integer,Integer> map = new HashMap();
int[] array = new int[2];
int temp = 0;
for(int num: nums){
temp = temp^num ;
map.put(num,num);
}
int test =0;
for(int num: nums){
test = temp^num;
if(num != test &&map.containsKey(test)){
array[0] = num;
array[1] = test;
break;
}
}
return array;
}
可惜速度提高3ms,击败百分比还是太少

最优解法:别人的
public int[] singleNumber(int[] nums) {
int xor = 0;
for (int i : nums)// 一样的抵消,不一样的两个数字异或运算结果必定有一位是1
xor ^= i;
int mask = xor & (-xor);
int[] ans = new int[2];
for (int i : nums) {
if ((i & mask) == 0)//== 0、 == mask 两种结果
ans[0] ^= i;
else
ans[1] ^= i;
}
return ans;
}
结题思路(别人的)
第一步:
把所有的元素进行异或操作,最终得到一个异或值。因为是不同的两个数字,所以这个值必定不为0;
int xor = 0;
for (int num : nums) {
xor ^= num;
}
以[1,2,1,3,2,5]为输入,可以等00000110
第二步:
这个数字和相反数做与运算得到一个二进制位最右边一位为1的数字,(全部异或之后值其实就是两个不同数异或,位置上面为1则表示两个数字在这一位上不同。) 注解:主要这部分没有想到可以通过这种求得到
int mask = xor & (-xor);
00000110 & (11111010) = 00000010 负数则正数取反码+1 = 11111001 + 1
第三步:
通过与这个mask进行与操作,如果为0的分为一个数组,为1的分为另一个数组。这样就把问题降低成了:“有一个数组每个数字都出现两次,有一个数字只出现了一次,求出该数字”。对这两个子问题分别进行全异或就可以得到两个解。也就是最终的数组了。 (注解:该步骤就要求两个数字(在数组中只出现一次)在哪一位不同,然后使用&将这两数分为两组)
int[] ans = new int[2];
for (int num : nums) {
if ( (num & mask) == 0) {
ans[0] ^= num;
} else {
ans[1] ^= num;
}
}
使用00000010通过&将数据分为两组。由于上面得知等于1这一位在两个数的数值也不一样,则可以区分成两组。 复杂度分析: 时间复杂度O(N),空间复杂度O(1)