题目来源:https://www.nowcoder.com/practice/e02fdb54d7524710a7d664d082bb7811?tpId=13&&tqId=11193&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
答案来源:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/solution/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-by-leetcode/
题目描述
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
解题思路
异或方法
-
相同数字,异或结果为0
两个相同的数,转换为二进制后,每一位都相同,根据异或规则,对应位相同为0,所以,两个相同的数做异或操作,结果为0.
也就是说,数组中出现两次的数字,经异或后,结果为0。 -
整个数组异或,相当于,两个只出现一次数字异或
本题中,除了两个数字(a,b)外,每个数字出现了两次,也就是说,整个数组异或的结果,即不相同的两个数a,b
异或结果,记作res
现在的问题在于:如何根据异或结果,区分这两个数字?
我们都知道,异或规则为相同为0,相异为1。
我们的目标是找到一个分组的规则,使得
- 两个只出现一次的数字在不同组——A组,B组
- 出现两次的数字在相同组——可以是A组,也可以是B组,只要确保相同数字在一个组即可(相同数字二进制表示相同,分别与另一个数字进行某种运算,结果也相同)
- 最后,分别对每一组进行异或操作,得到只出现一次的数字a,b
那么,怎么实现呢?
- 首先根据异或结果,找到以哪个数位为依据,进行分组。
从右向左,找到两个只出现一次数字的第一个不同位是哪一个
也就是,找到异或结果,从右向左,第一个为1的数位,利用该位进行分组(左移运算,得到…1…,省略号部分为0,比如0001/0010/0100等),即flag - 遍历数组,当前值与flag 进行并运算,结果为0的放在A组,结果为1的放在B组
- A组整体进行异或,得到a,B组整体进行异或,得到b。
- a,b即为所求。
代码实现—Java
//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
import java.util.*;
public class Solution {
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
//特殊情况
if(array.length<=1||array==null)
return;
//异或方法
//两个相同的数每一位都相同,根据异或规则,相同为0,所以,两个相同的数做异或操作,结果为0
//本题中,除了两个数字(a,b)外,每个数字只出现了一次,也就是说,整个数组异或的结果,即不相同的两个数异或结果
//现在的问题在于:如何根据异或结果,区分这两个数字
//1.整个数组进行异或操作,要想进行异或操作,数组中至少有两个数字
//当数组中有两个数字时,即为只出现一次的两个数字
if(array.length==2){
num1[0] = array[0];
num2[0] = array[1];
}
int res = 0;
for(int i = 0;i<array.length;i++){
res = res^array[i];
}
//此时,res为两个只促销一次数字异或的结果
//2.根据异或结果,找到这两个数字,从右至左,第一个不同的数位是哪一个
int flag = 1;//0001
//当flag 和 res 进行与运算,结果为0时,flag向左移1位
while((res&flag)==0)
flag = flag<<1;
//3.根据flag,实现数组分组
//即 和flag 进行与运算,为0的放在一组,非0放在一组
//结果初始化
num1[0] = 0;
num1[0] = 0;
for(int i=0;i<array.length;i++){
if((flag & array[i])==0){
num1[0] = num1[0]^array[i];
}
else
num2[0] = num2[0]^array[i];
}
}
}
时间复杂度:O(n)
空间复杂度:O(1)