数组中只出现1次的两个数
程序员文章站
2024-03-15 09:26:35
...
思路
首先我们可以考虑下这个题目的简化版——数组中除一个数字只出现1次外,其它数字都成对出现,要求尽快找出这个数字。
根据异或运算的特点,直接异或一次就可以找出这个数字。
现在数组中有两个数字只出现1次,直接异或一次只能得到这两个数字的异或结果,但光从这个结果肯定无法得到这个两个数字。因此我们来分析下简化版中“异或”解法的关键点,这个关键点也相当明显——数组只能有一个数字出现1次。
设题目中这两个只出现1次的数字分别为A和B,如果能将A,B分开到二个数组中,那显然符合“异或”解法的关键点了。因此这个题目的关键点就是将A,B分开到二个数组中。由于A,B肯定是不相等的,因此在二进制上必定有一位是不同的。根据这一位是0还是1可以将A,B分开到A组和B组。而这个数组中其它数字要么就属于A组,要么就属于B组。再对A组和B组分别执行“异或”解法就可以得到A,B了。而要判断A,B在哪一位上不相同,只要根据A异或B的结果就可以知道了,这个结果在二进制上为1的位都说明A,B在这一位上是不相同的。
所有元素异或后,在找出最右边为1的时,主要利用正数原码和其对应相反数补码
的关系:
对于一个数字x,x&(-x)之后得到的数字,是把x中最右边的1保留下来,其他位全部为0。注意,这里的-x是x的相反数。
package com.zhumq.leetcode;
import java.util.Arrays;
import org.junit.Test;
public class FindTwoNumsAppearOnce {
/*
* 返回一个数最低位的1,其他的各位都为0,利用补码和原码的关系
*/
public int FindFirstBit1(int num) {
return num&(-num);
}
/**
* 根据res判断n的特定位是否为1
* @param n
* @param res res中只有一位为1,是前面FirstBit1的返回值
* @return
*/
public boolean IsBit1(int n,int res) {
return ((n&res)==0) ? false:true;
}
public int[] FindNumsAppearOnce(int arr[])
{
//数组为空或者元素个数下于2时返回null
if(arr==null || arr.length<2)
return null;
//全部异或之后的值,即A,B异或后的值
int allXor = 0;
//全部异或,allXor保存异或的结果
for(int i=0;i<arr.length;i++)
allXor ^= arr[i];
//最低位的1
int res = FindFirstBit1(allXor);
int num1 =0, num2 = 0;
for(int i=0;i<arr.length;i++)
{
/*
* 根据最低位是否为1来划分异或
* 相同的数字相同的位上的值是相同的,要么都为1,要么都为0,
* 因此同样可以通过判断该位是否为1来将这些出现两次的数字划分到同一个子数组中,
* 该位如果为1,就分到一个子数组中,如果为0,就分到另一个子数组中。
* 不管划分的是否均匀,只要保证来个相同的元素分到同一边即可,即使两两相同的元素全分到同一边异或后结果也还是一样。
*/
if(IsBit1(arr[i],res))
num1 ^= arr[i];
else
num2 ^= arr[i];
}
return new int[] {num1,num2};
}
@Test
public void test1() {
int arr[] = {1,2,3,3,1,4,5,4,5,6};
System.out.println(Arrays.toString(FindNumsAppearOnce(arr)));
}
}