【劍指offer】陣列中只出現一次的數字(2)

蘭亭風雨發表於2014-05-31

轉載請註明出處:http://blog.csdn.net/mmc_maodun/article/details/27800577


    題目:一個int陣列中有三個數字abc只出現一次,其他數字都出現了兩次。請找出三個只出現一次的數字。

    上篇博文中我們求的是兩個只出現一次的數字,且時間複雜度為O(n),這次是三個,可以同樣考慮將陣列先分成兩個子陣列,求出其中一個只出現一次的數字,而後再將另一個子陣列分成兩個子陣列,再分別求這兩個只出現一次的數字。何海濤的部落格給的就是這種思路,並給出了詳細的證明過程,詳見:http://zhedahht.blog.163.com/blog/static/25411174201283084246412/,但該方法真的要自己去想,很難想到,而且如果是面試的話,很難給面試官講懂。下面介紹另外一種方法,該方法具一定的通用性,對於2個,3個出現一次的數字這類的問題,都可以按照該思路去求解,只是時間複雜度可能要稍微大些,為O(8*sizeof(int)*n),8*sizeof(int)其實即使int的位,在一般的32位系統中,它為32,而n自然就是陣列的長度了。

    該方法的思路如下:

    首先由於有3個數字出現一次,其他的都出現兩次,所以n肯定為奇數,該方法通過掃描整數的每一位來逐個判斷。

    再看這3個只出現一次的數字,他們的bit位肯定不可能全部相同,也就是說,雖然有些bit位上的數可能相等,但肯定至少存在某一個bit位,這三個數中,有兩個數的該bit位為1,一個數的該bit位為0,或者兩個數的該bit位為0,一個數的該bit位為1。我們可以通過掃面int的所有bit位,掃描每個bit位的時候,遍歷陣列,如果能找出符合上面條件的,我們就可以找出其中的一個只出現一次的數字,該數字與另外兩個只出現一次的數的bit位不同。找到一個之後,就可以將其與陣列的最後一個元素交換,再在前面n-1個數中找出另外兩個就可以了,方法的話,可以直接用上篇博文中介紹的方法,也可以用該博文介紹的思路。

    下面要來看下如果找出這個與另外兩個數的該bit位不同的數。

    先看第一種情況,如果a,b,c三個數中,有兩個該bit位為0,另一個為1,我們遍歷陣列,分別統計該陣列元素中該bit位為1和0的元素個數,分別設為count1和count0,並同時將所有該bit位為1的元素異或,所有該bit位為0的元素異或,得到的結果分別設為temp1和temp0。如果count1為奇數,則可能有兩種情況,a,b,c三個數的該bit位全為1,或者有兩個為0,一個為1,如果有temp0==0,則說明是前一種情況(a,b,c的該bit位全為1的話,所有該bit位為0的每個元素出現了兩次,因此異或後的結果為0),此時沒法找出其中的一個數,則直接跳到下次迴圈,繼續判斷下一個bit位,如果temp0!=0,則說明是後一種情況(說明該比bt位為0的元素異或後沒有完全抵消,則說明有一個元素是隻出現一次的),此時其中一個只出現一次的數字就是temp0(重複的元素異或後都抵消了)。

    第二種情況,是兩個該bit位為1,另一個為0的情況,分析思路與上面的類似。

    很明顯,這種掃描每個bit位來進行判斷的思路可以解決整個的這一類問題,自然也可以求出上篇博文中兩個只出現一次的數字。具體思路不再給出,主要的判斷依據,一般都是bit位為1或0的數字的個數的奇偶,bit位為1的元素異或,bit位為0的元素異或後的結果是否為0的判斷。

    下面給出這道題目的完整程式碼:

#include<stdio.h>

/*
通過掃面每一位,先找出一個只出現一次的數
*/
int FindOneNumAppearOnce(int *arr,int len)
{
	int count1 = 0;	//某一位上1的個數
	int count0 = 0; //某一位上0的個數
	int temp1 = 0;	//某一位為1的所有數相異或的結果
	int temp0 = 0;	//某一位為0的所有數相異或的結果

	int i,j;
	for(i=0;i<8*sizeof(int);i++)	//迴圈計算每一位的以上四個資料
	{
		count1 = count0 = temp1 = temp0 = 0;//每次計算下一位時清零
		for(j=0;j<len;j++)
		{	
			//每次向左移一位進行計算
			if(arr[j] & (1<<i))	//該位為1時
			{
				temp1 ^= arr[j];
				count1++;
			}
			else
			{
				temp0 ^= arr[j];
				count0++;
			}
		}

		if(temp1 & 1)	//某位上有奇數個1
		{
			if(temp0 == 0)	//此時3個不同數的該位都為1
				continue;
			else			//此時3個不同數的該位有1個1,2個0
				return temp1;
		}
		else	//某位上有偶數個1
		{
			if(temp1 == 0)	//此時3個不同數的該位都為0
				continue;
			else			//此時3個不同數的該位有1個0,2個1
				return temp0;
		}
	}
}

/*
返回num的最低位的1,其他各位都為0
*/
int FindFirstBit1(int num)
{
	//二者與後得到的數,將num最右邊的1保留下來,其他位的全部置為了0
	return num & (-num);
}

/*
判斷data中特定的位是否為1,
這裡的要判斷的特定的位由res確定,
res中只有一位為1,其他位均為0,由FindFirstBit1函式返回,
而data中要判斷的位便是res中這唯一的1所在的位
*/
bool IsBit1(int data,int res)
{
	return ((data&res)==0) ? false:true;
}

void FindTwoNumsAppearOnce(int *arr,int len,int *num1,int *num2)
{

	int i;
	int AllXOR = 0;
	//全部異或
	for(i=0;i<len;i++)
		AllXOR ^= arr[i];

	int res = FindFirstBit1(AllXOR);

	*num1 = *num2 = 0;
	for(i=0;i<len;i++)
	{
		if(IsBit1(arr[i],res))
			*num1 ^= arr[i];
		else
			*num2 ^= arr[i];
	}
}

/*
交換兩個int變數
*/
void Swap(int *a,int *b)
{
	if(*a != *b)
	{
		*a ^= *b;
		*b ^= *a;
		*a ^= *b;
	}
}

/*
找出這三個只出現一次的數字
*/
void FindThreeNumsAppearOnce(int *arr,int len,int *num1,int *num2,int *num3)
{
	if(arr==NULL || len<3)
		return;

	*num1 = FindOneNumAppearOnce(arr,len);

	//找到第一個找出的數字,並與最後一個元素交換,便於接下來剩下的兩個數字
	int i;
	for(i=0;i<len;i++)
		if(*num1 == arr[i])
			break;
	Swap(&arr[i],&arr[len-1]);

	FindTwoNumsAppearOnce(arr,len-1,num2,num3);
}


int main()
{
	static int arr[1000000];
	int n;
	while(scanf("%d",&n) != EOF)
	{
		int i;
		for(i=0;i<n;i++)
			scanf("%d",arr+i);

		int num1,num2,num3;
		FindThreeNumsAppearOnce(arr,n,&num1,&num2,&num3);
		printf("%d %d %d\n",num1,num2,num3);
	}
	return 0;
}
    測試結果:



相關文章