首页 > 试题广场 >

数组中只出现一次的数字

[编程题]数组中只出现一次的数字
  • 热度指数:450647 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 64M,其他语言128M
  • 算法知识视频讲解
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
推荐
class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
  if(data.size()<2)
   return ;
  int size=data.size();
  int temp=data[0];
  for(int i=1;i<size;i++)
   temp=temp^data[i];
  if(temp==0)
   return ;
  int index=0;
  while((temp&1)==0){
   temp=temp>>1;
   ++index;
  }
  *num1=*num2=0;
  for(int i=0;i<size;i++)
  {
   if(IsBit(data[i],index))
    *num1^=data[i];
   else
    *num2^=data[i];
  }
    }
 bool IsBit(int num,int index)
 {
  num=num>>index;
  return (num&1);
 }
};
可以用位运算实现,如果将所有所有数字相异或,则最后的结果肯定是那两个只出现一次的数字异或
 的结果,所以根据异或的结果1所在的最低位,把数字分成两半,每一半里都还有只出现一次的数据和成对出现的数据
 这样继续对每一半相异或则可以分别求出两个只出现一次的数字
编辑于 2015-08-18 23:24:57 回复(77)
首先:位运算中异或的性质:两个相同数字异或=0一个数和0异或还是它本身

只有一个数出现一次时,我们把数组中所有的数,依次异或运算,最后剩下的就是落单的数,因为成对儿出现的都抵消了。

依照这个思路,我们来看两个数(我们假设是AB)出现一次的数组。我们首先还是先异或,剩下的数字肯定是A、B异或的结果,这个结果的二进制中的1,表现的是A和B的不同的位。我们就取第一个1所在的位数,假设是第3位,接着把原数组分成两组,分组标准是第3位是否为1。如此,相同的数肯定在一个组,因为相同数字所有位都相同,而不同的数,肯定不在一组。然后把这两个组按照最开始的思路,依次异或,剩余的两个结果就是这两个只出现一次的数字。
public class Solution {
    public void FindNumsAppearOnce(int[] array, int[] num1, int[] num2)    {
        int length = array.length;
        if(length == 2){
            num1[0] = array[0];
            num2[0] = array[1];
            return;
        }
        int bitResult = 0;
        for(int i = 0; i < length; ++i){
            bitResult ^= array[i];
        }
        int index = findFirst1(bitResult);
        for(int i = 0; i < length; ++i){
            if(isBit1(array[i], index)){
                num1[0] ^= array[i];
            }else{
                num2[0] ^= array[i];
            }
        }
    }
    
    private int findFirst1(int bitResult){
        int index = 0;
        while(((bitResult & 1) == 0) && index < 32){
            bitResult >>= 1;
            index++;
        }
        return index;
    }
    
    private boolean isBit1(int target, int index){
        return ((target >> index) & 1) == 1;
    }
}

发表于 2016-08-22 20:20:05 回复(59)
/*考虑过程:
 首先我们考虑这个问题的一个简单版本:一个数组里除了一个数字之外,其他的数字都出现了两次。请写程序找出这个只出现一次的数字。
 这个题目的突破口在哪里?题目为什么要强调有一个数字出现一次,其他的出现两次?我们想到了异或运算的性质:任何一个数字异或它自己都等于0 。也就是说,如果我们从头到尾依次异或数组中的每一个数字,那么最终的结果刚好是那个只出现一次的数字,因为那些出现两次的数字全部在异或中抵消掉了。
 有了上面简单问题的解决方案之后,我们回到原始的问题。如果能够把原数组分为两个子数组。在每个子数组中,包含一个只出现一次的数字,而其它数字都出现两次。如果能够这样拆分原数组,按照前面的办法就是分别求出这两个只出现一次的数字了。
 我们还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。因为其它数字都出现了两次,在异或中全部抵消掉了。由于这两个数字肯定不一样,那么这个异或结果肯定不为0 ,也就是说在这个结果数字的二进制表示中至少就有一位为1 。我们在结果数字中找到第一个为1 的位的位置,记为第N 位。现在我们以第N 位是不是1 为标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第N 位都为1 ,而第二个子数组的每个数字的第N 位都为0 。
 现在我们已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其它数字都出现了两次。因此到此为止,所有的问题我们都已经解决。*/
public class 数组中只出现一次的数字 {

  public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
       if(array==null ||array.length<2)
           return ;
       int temp = 0;
       for(int i=0;i<array.length;i++)
           temp ^= array[i];
        
       int indexOf1 = findFirstBitIs(temp);
       for(int i=0;i<array.length;i++){
           if(isBit(array[i], indexOf1))
               num1[0]^=array[i];
           else
               num2[0]^=array[i];
       }
   }
   public int findFirstBitIs(int num){
       int indexBit = 0;
       while(((num & 1)==0) && (indexBit)<8*4){
           num = num >> 1;
           ++indexBit;
       }
       return indexBit;
   }
   public boolean isBit(int num,int indexBit){
       num = num >> indexBit;
       return (num & 1) == 1;
   }

}

发表于 2015-09-15 10:20:18 回复(64)

Python Solution

两种解法:HashMap(容易想到) 和 异或法(技术流)

第二种参考了置顶答案

# -*- coding:utf-8 -*-
"""
hashMap法
class Solution:
    # 返回[a,b] 其中ab是出现一次的两个数字
    def FindNumsAppearOnce(self, array):
        hashMap = {}
        for i in array:
            if str(i) in hashMap:
                hashMap[str(i)] += 1
            else:
                hashMap[str(i)] = 1
        res = []
        for k in hashMap.keys():
            if hashMap[k] == 1:
                res.append(int(k))
        return res
"""


class Solution:
    def FindNumsAppearOnce(self, array):
        if not array:
            return []
        # 对array中的数字进行异或运算
        tmp = 0
        for i in array:
            tmp ^= i
        # 获取tmp中最低位1的位置
        idx = 0
        while (tmp & 1) == 0:
            tmp >>= 1
            idx += 1
        a = b = 0
        for i in array:
            if self.isBit(i, idx):
                a ^= i
            else:
                b ^= i
        return [a, b]

    def isBit(self, num, idx):
        """
        判断num的二进制从低到高idx位是不是1
        :param num: 数字
        :param idx: 二进制从低到高位置
        :return: num的idx位是否为1
        """
        num = num >> idx
        return num & 1
发表于 2018-02-20 21:44:39 回复(14)
/**
	 * 数组中有两个出现一次的数字,其他数字都出现两次,找出这两个数字
	 * @param array
	 * @param num1
	 * @param num2
	 */
	public static void findNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        if(array == null || array.length <= 1){
        	num1[0] = num2[0] = 0;
        	return;
        }
        int len = array.length, index = 0, sum = 0;
        for(int i = 0; i < len; i++){
        	sum ^= array[i];
        }
        for(index = 0; index < 32; index++){
        	if((sum & (1 << index)) != 0) break;
        }
        for(int i = 0; i < len; i++){
        	if((array[i] & (1 << index))!=0){
        		num2[0] ^= array[i];
        	}else{
        		num1[0] ^= array[i];
        	}
        }
    }
/**
	 * 数组a中只有一个数出现一次,其他数都出现了2次,找出这个数字
	 * @param a
	 * @return
	 */
	public static int find1From2(int[] a){
		int len = a.length, res = 0;
		for(int i = 0; i < len; i++){
			res = res ^ a[i];
		}
		return res;
	}
/**
	 * 数组a中只有一个数出现一次,其他数字都出现了3次,找出这个数字
	 * @param a
	 * @return
	 */
	public static int find1From3(int[] a){
		int[] bits = new int[32];
		int len = a.length;
		for(int i = 0; i < len; i++){
			for(int j = 0; j < 32; j++){
				bits[j] = bits[j] + ( (a[i]>>>j) & 1);
			}
		}
		int res = 0;
		for(int i = 0; i < 32; i++){
			if(bits[i] % 3 !=0){
				res = res | (1 << i);
			}
		}
		return res;
	}

发表于 2015-10-04 14:54:25 回复(25)
思路就是使用异或,但是与在成对出现的数字中查找一个单独的数字不同的是需要利用异或结果的最低位为1的flag将数组中的数字分为两类,一类是与flag按位与为0,另一类为不为0,这样再分别异或一次就能够找出这两个数。很是巧妙。其中有一个语法上容易忽略的坑:==的优先级比&高,所以&时需要加括号。

class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
        if(data.size() < 2) return ;
		int myxor = 0;
        int flag = 1;
        for(int i = 0 ; i < data.size(); ++ i )
            myxor ^= data[i];
        while((myxor & flag) == 0) flag <<= 1;
        *num1 = myxor;
        *num2 = myxor;
        for(int i = 0; i < data.size(); ++ i ){
            if((flag & data[i]) == 0) *num2 ^= data[i];
            else *num1 ^= data[i];
        }
    }
};

发表于 2015-09-06 22:15:34 回复(7)
思路:
1、异或思想,一个数与自己异或为0,一个数与0异或为自己
2、由于其它数字两两相同,所以所有数异或则得到这两个不同数的异或结果。取这个结果的第一个1作为标志位
3、这个标志的1,必须是:这两个数在该位一个为0,一个为1
4、这样可以将数组分为两组,一组在该标志位为1,一组在该标志位为0,这两个不同数字分别在这两组内
5、将两组内的数分别异或,得到两个结果则为这两个不同的数
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        int num=0;
        for(int i=0;i<array.length;i++){
            num^=array[i];//所有数异或,结果为不同的两个数字的异或
        }
        int count=0;//标志位,记录num中的第一个1出现的位置
        for(;count<array.length;count++){
            if((num&(1<<count))!=0){
                break;
            }
        }
        num1[0]=0;
        num2[0]=0;
        for(int i=0;i<array.length;i++){
            if((array[i]&(1<<count))==0){//标志位为0的为一组,异或后必得到一个数字(这里注意==的优先级高于&,需在前面加())
                num1[0]^=array[i];
            }else{
                num2[0]^=array[i];//标志位为1的为一组
            } 
        }    
    }
}
编辑于 2017-02-08 12:37:47 回复(9)
此题考察的是异或运算的特点:即两个相同的数异或结果为0。
此题用了两次异或运算特点:
(1)第一次使用异或运算,得到了两个只出现一次的数相异或的结果。
(2)因为两个只出现一次的数肯定不同,即他们的异或结果一定不为0,一定有一个位上有1。另外一个此位上没有1,我们可以根据此位上是否有1,将整个数组重新划分成两部分,一部分此位上一定有1,另一部分此位上一定没有1,然后分别对每部分求异或,因为划分后的两部分有这样的特点:其他数都出现两次,只有一个数只出现一次。因此,我们又可以运用异或运算,分别得到两部分只出现一次的数。
发表于 2015-09-22 22:37:05 回复(14)
//异或的方法
class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
		int diff=accumulate(data.begin(),data.end(),0,bit_xor<int>());
        diff&=-diff;  //即找到最右边1-bit
        *num1=0;*num2=0;
        for (auto c:data) {
            if ((c&diff)==0) *num1^=c;
            else *num2^=c;
        }
    }
};
//哈希表
class Solution {
public:
   void FindNumsAppearOnce(vector<int> data, int* num1, int *num2) {
	unordered_map<int, int> map;
	for (int i = 0; i < data.size(); i++) 
		map[data[i]]++;
	vector<int>v;
	for (int i = 0; i < data.size(); i++) 
		if (map[data[i]]== 1)
			v.push_back(data[i]);
	*num1 = v[0]; *num2 = v[1];
	}
};

编辑于 2016-09-25 19:09:59 回复(10)
import java.util.ArrayList;
public class Solution {
	    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
	    		ArrayList<Integer>list=new ArrayList<Integer>();
	    		for(int i=0;i<array.length;i++)
	    			{
	    				if(!list.contains(array[i]))
	    					list.add(array[i]);
	    				else 
	    					list.remove(new Integer(array[i]));
	    			}
	    		if(list.size()>1)
	    			{
	    				num1[0]=list.get(0);
	    				num2[0]=list.get(1);
	    			}
	    }
}

发表于 2015-09-03 01:35:12 回复(13)
//最简短的代码。基本思想是将这个数组一分为二,而这个分法是有讲究的:
//必须将这两个不同的数字分到两个不同数组里,这可以根据这两个数中任意不同的位来划分。
class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
		int Xor = 0;
        for(int i = 0; i < data.size(); ++i)
            Xor ^= data[i];
		//int split = Xor&(Xor-1)^Xor; //找到两个只出现一次的数字的第一个位值不同的位置
        int split = Xor&~(Xor - 1);
        for(int i = 0; i < data.size(); ++i){
            if(data[i]&split) *num1 ^= data[i];
            else *num2 ^= data[i];
        }
    }
};

编辑于 2017-06-03 21:52:11 回复(4)

python只需要一行代码:

from collections import Counter
class Solution:

    def FindNumsAppearOnce(self, array):


        return list(map(lambda c:c[0],Counter(array).most_common()[-2:]))
发表于 2017-10-07 16:12:36 回复(32)
这道题用set查找很方便啊,遍历输入的vector,当前数字不在set中则插入,否则从set删除(出现两次的数字),这样结束遍历后set中剩余的就是要找的那两个只出现了一次的数字。
class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
        set<int> save;
        set<int>::iterator iter;
        for (int i = 0; i < data.size(); i++){
            if (save.find(data[i]) == save.end())
                save.insert(data[i]);
            else{
                iter = save.find(data[i]);
                save.erase(iter);
            }
        }
        iter = save.begin();
        *num1 = *iter;
        *num2 = *(++iter);
    }
};

发表于 2016-08-29 16:48:08 回复(11)
//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
import java.util.*;
import java.util.Map.Entry;
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        if(array==null&&array.length<=1){
            num1[0]=num2[0]=0;
            return;
        }
        HashMap<Integer,Integer> map = new HashMap<>();
        for(int i=0;i<array.length;i++){
            if(map.containsKey(array[i])){
                map.put(array[i],2);
            }
            else{
                map.put(array[i],1);
            }
        }
        num1[0]=0;
        for(Entry entry:map.entrySet()){
            if((Integer)entry.getValue()==1){
                if(num1[0]==0){
                    num1[0]=(Integer)entry.getKey();
                }else{
                    num2[0]=(Integer)entry.getKey();
                }
            }
        }
    }
}


发表于 2017-08-27 21:39:18 回复(4)

题目描述

一个整型数组里除了两个数字之外,其他的数字都出现了偶数次。请写程序找出这两个只出现一次的数字。

解题思路

一种简单的思路,可以想到用HashSet这种数据结构来存,重复的就立即剔除,剩下的就是不重复的两个数字,将其取出即可。

//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
import java.util.*;
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        HashSet<Integer> set = new HashSet<>();
        for(int i=0;i<array.length;i++){
            if(!set.isEmpty() && set.contains(array[i])){
                set.remove(array[i]);
            }else{
                set.add(array[i]);
            }
        }
        //这边处理的不够好
        ArrayList<Integer> list = new ArrayList<>();
        if(set.size() == 2){
            for(Integer i:set){
                list.add(i);
            }
        }

        num1[0] = list.get(0);
        num2[0] = list.get(1);
    }
}

但是对于题目中说除了两个单个数字外,其他的都出现偶数次。我们需要从这句话入手,寻求更优的解决思路。

我们知道,位运算中异或的性质是:两个相同数字异或=0,不相同的话肯定不为0,一个数和0异或还是它本身。

这个题目的突破口在哪里?题目为什么要强调有一个数字出现一次,其他的出现两次?我们想到了异或运算的性质:任何一个数字异或它自己都等于0 。也就是说,如果我们从头到尾依次异或数组中的每一个数字,那么最终的结果刚好是那个只出现一次的数字,因为那些出现两次的数字全部在异或中抵消掉了。

有了上面简单问题的解决方案之后,我们回到原始的问题。如果能够把原数组分为两个子数组。在每个子数组中,包含一个只出现一次的数字,而其它数字都出现两次。如果能够这样拆分原数组,按照前面的办法就是分别求出这两个只出现一次的数字了。

我们还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。因为其它数字都出现了两次,在异或中全部抵消掉了。由于这两个数字肯定不一样,那么这个异或结果肯定不为0 ,也就是说在这个结果数字的二进制表示中至少就有一位为1 。

我们在结果数字中找到第一个为1 的位的位置,记为第N 位。现在我们以第N 位是不是1 为标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第N 位都为1 ,而第二个子数组的每个数字的第N 位都为0 。

现在我们已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其它数字都出现了两次。因此到此为止,所有的问题我们都已经解决。

//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        //【解决思路】:从简单的场景想起,假设一个数组中只有一个独特元素,其他出现次数都为2
        //如何快速找出这个独特元素呢?那就是从头到尾两两异或,由于相同的数异或为0,则认为是抵消
        //一直到最后,结果必然就是这个独特元素
        //那么找出两个来也是这个思路,核心就是要将这两个独特的数分离开,下面详细介绍
        if(array == null || array.length <= 1){
            num1[0] = num2[0] = 0;
            return;
        }

        //整个数组从头两两异或,最终的结果必然是两个不同数字的异或结果
        //因为相同的数字两两异或之后为0
        //0和任意一个数异或还是这个数本身
        int len = array.length, index = 0, sum = 0;
        for(int i = 0; i < len; i++){
            sum ^= array[i];
        }

        //java中int类型占4个字节,即32个bit
        //从左开始找到这个异或结果第一个为1的索引
        while((sum&1) == 0 && index < 32){
            sum = sum >> 1;
            index++;
        }

        //以这个索引处是否为1作为判定标准,就将两个不同的数分离开了
        //下面就是分两批不停地疑惑,就会得到这两个不同的数
        for(int i = 0; i < len; i++){
            //这样就可以分别找到index处为1的独特解以及为0的独特解
            if(isBit(array[i],index)){
                num1[0] ^= array[i];
            }else{
                num2[0] ^= array[i];
            }
        }
    }

    //判断num的index(从左往右看)是否为1
    private boolean isBit(int num,int index){
        num = num >> index;
        if((num & 1) == 1){
            return true;
        }else{
            return false;
        }
    }


}
编辑于 2019-03-11 13:29:52 回复(3)

利用set解决问题,简单明了

public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        HashSet<Integer> set = new HashSet<Integer>();

        for (int i = 0;i < array.length;i++){
            if(!set.add(array[i])){
                set.remove(array[i]);
            }
        }

        Object[] temp =set.toArray();
        num1[0] = (int) temp[0];
        num2[0] = (int) temp[1];
    }
发表于 2018-01-05 14:34:06 回复(4)
class Solution {
public:
    //思路:用异或的特性,A^A=0 0^X=X;以及异或的交换律特性。
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
		int x=0,len=data.size();
        for(int i=0;i<len;++i)
            x^=data[i];//x保存所有元素异或的结果
        int n=1;
        while((x & n)==0)//找出最右边第1个不为0的位置
            n=n<<1;
        int x1=0,x2=0;
        for(int i=0;i<len;++i)
            if(data[i] & n)//根据第一个不为0的位置重新将数组进行划分
                x1^=data[i];
        	else
                x2^=data[i];
        *num1=x1;
        *num2=x2;
        return ;
    }
};

发表于 2017-08-03 16:45:54 回复(5)

//使用堆栈来做辅助功能,将数组先排序,依次入栈,每一次数组入栈时和当前堆栈的栈头比较,如果当前堆栈为空,就入栈,如果和当前栈头的元素相同就出栈,当数组中左右元素都入栈完毕,那么当前栈中剩余的2个元素就是只出现一次的两个元素

public static void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {

    Arrays.sort(array);
    Stack<Integer> stack = new Stack<Integer>();
    int len = array.length;

    if(array == null){
        num1[0] = 0;
        num2[0] = 0;
    }


    for(int x = 0;x<len;x++){
        if(stack.isEmpty()){
            stack.push(array[x]);

        }else{
            if(stack.peek() == array[x])
                stack.pop();
            else
                stack.push(array[x]);
        }

    }

    num1[0] = stack.pop();
    num2[0] = stack.pop();


}

}

发表于 2017-07-13 16:30:13 回复(14)
Java哈希表
import java.util.*;
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        Queue<Integer> arr = new LinkedList<Integer>();
        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int i = 0; i < array.length; i++) {
            if (!map.containsKey(array[i])) {
                map.put(array[i], 1);
            } else {
                map.put(array[i], map.get(array[i]) + 1);
            }
        }
        for (int i = 0; i < array.length; i++) {
            if (map.get(array[i]) == 1) {
                arr.add(array[i]);
            }
        }
        num1[0] = arr.poll();
        num2[0] = arr.poll();
        System.out.println(num1[0]);
        System.out.println(num2[0]);
    }
}

发表于 2018-07-11 15:39:42 回复(1)
import java.util.Arrays;
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        
        if(array.length==1){num1[0]=0;num2[0]=0;return;}
        Arrays.sort(array);
        int i,j,length = array.length;
        
        for(i=0;i<length;i+=2)
            if(array[i]!=array[i+1])break;        
        num1[0]=array[i];
        
        for(j=i+1;j<length-2;j+=2)
            if(array[j]!=array[j+1])break;
        num2[0]=array[j];
        
    }
}

发表于 2016-07-01 18:47:16 回复(3)
  算法的精妙之处:异或。相同的数异或之后为0,所有数组中两两相同的数异或之后,抵消为0
public class Solution {
    public void FindNumsAppearOnce(int[] array, int num1[], int num2[]) {
		if (array == null || array.length < 2)
			return;
		int resultExclusiveNor = 0;
		for (int item : array)
			resultExclusiveNor ^= item;
		int firstIndex = findFirstIndex(resultExclusiveNor);
		num1[0]=0;
		num2[0]=0;
		for(int item:array){
			if(isBit1(item,firstIndex))
				num1[0]^=item;
			else
				num2[0]^=item;
		}

	}

	// 二进制数 从右往左 找到第一个 "1"
	public int findFirstIndex(int n) {
		int index = 0;
		while ((1 & n) == 0 && index < 32) {
			n = n >> 1;
			index++;
		}
		return index;
	}
    //判断这个数的二进制形式从左到右index位是否为"1"
	public boolean isBit1(int num, int index) {
		boolean check = false;
		num = num >> index;
		if ((num & 1) == 1)
			check = true;
		return check;

	}
}

发表于 2016-05-29 21:17:06 回复(0)