1. 程式人生 > >位運算之只出現一次的的數字

位運算之只出現一次的的數字

******位運算系列之陣列中只出現一次的數字******

//題目(1):在一個數組中只有一個數字出現一次,其他數字都是成對出現的!讓你找出這個只出現一次的數字,
//其實,這也叫缺失的數字,用來找成對陣列中裡有一對數字缺失了一個數字的陣列成員!
/* 那麼,現在先來分析一下,一般情況下,我們最先想到的方法就是統計一下陣列中每個成員出現的次數,然後
找出那個只出現了一次的數字,不可否認的是方法是可行的,可是效率問題就出來了,這種方法的時間複雜度
是O(n^2);看來並不是解決問題的最佳方案,還有同學會提出可以先將陣列排序,然後比較相鄰兩個數是否相同
,可以找出這個單獨出現一次的數,可是考慮一下排序的時間複雜度,顯然也不是最優方案,這兩種解決方法
大家可以自己試一試,才能感覺到不同!
那麼,想想我們知道的位操作符,想想用二進位制是否可以解決這個問題; 1.) 先回憶一下異或運算子的作用,(^) 處理二進位制中兩個數的異或時,相同為0,相異為1,異或還有交換的作用;
那麼我們假設陣列是{2,3,2,4,4},陣列中成對的成員不一定是在一起的,我們先把他們的二進位制列出來:
@我就不把32位都列出來了!
2:0010
3:0011
2:0010
4:0100
4:0100
既然相同位異或為0,那麼讓陣列中的全部元素異或後的結果不就是那個單獨出現一次的數字了;
0010^0011 = 0001,0001^0010 = 0011(注意,這塊是不是又變回3了),0011^0100 = 0111,0111^0100 = 0011(最後的結果還是3)
那麼方法是有了,開始實現程式碼吧!
*/
//假設陣列是 arr[7] = {2,3,4,2,4};
#include<stdio.h>
#include<stdlib.h>

int FindOnce_Number(int arr[], int sz)
{
	int i = 0;
	int num = 0;

	for(i = 0; i < sz; i++)
	{
		num ^= arr[i];
	}
	return num;
}

int main()
{
	int arr[] = {2,3,4,2,4};
	int sz = sizeof(arr)/sizeof(arr[0]);
	int num = FindOnce_Number(arr,sz);

	printf("單獨出現一次的數字為:> %d\n",num);
	system("pause");
	return 0;
}
@ 結果: @如果這道題不過癮的話,那麼就繼續下面這道題!
題目(2): 一個數組中,只有一個數字出現了一次,其他數字都出現了三次,找出這個出現了一次的數字;
/* 分析: 這是前一道題的一個變種,我第一次做這道題時也是懵逼的,我的方法是用雜湊表來求解,類似於
求字串中的第一個只出現一次的字元,雜湊表是最佳解法,但是對於這道題來說,雜湊表就顯得無力了,比如
讓你找的數字是100000,難道你要開闢100000個int的空間嗎,多麼的奢侈與浪費啊,不清楚雜湊表的同學,可以
查查資料,那麼,這道題可以和前面的解法一樣嗎?將整個陣列異或一遍?  顯然是不可行的,因為其他成員都是
出現了三次,異或也消除不了啊,不信你可以試試!  那可真是頭痛,  靜下心心來分析分析:
@以{2,3,2,4,2,4,4}為例 2:0010
3:0011
2:0010
4:0100
2:0010
4:0100
4:0100 這樣列出來的話,可能半天看不出來有什麼特點,那麼這樣列出來呢: 2:0010
2:0010
2:0010
4:0100
4:0100
4:0100
3:0011 這樣列出來的話是不是不要太明顯,如果沒有3的話,出現三次的數的二進位制上的1就能被3整除,
那麼,加入了3之後是不是破壞了原有的平衡,只要找出每一位1的出現不能被3整出的位,那單獨
出現一次的數的這一位肯定是1,其他位如果1只出現了一次,那肯定是單獨數上的1;那麼,問題
是不是變得多了! 以後再有找只有一個出現一次的數,其他數都是>=3 次出現,都是這樣求解!  下面我們用程式碼來實現!
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//難點在於找出所有數的所有位的1的個數,用較為簡單的方法,以下方法為我學習大神的方法!特別棒!
int FindOnce_Number(int arr[], int sz)
{
	int bit[32];
	int i = 0;
	int j = 0;
	int num = 0;
	memset(bit,0,32*sizeof(int));
	//記憶體操作函式,標頭檔案<string.h>,作用是將陣列bit全部置0;
	for(i =  0; i < sz; i++)
	for(j = 0; j < 32; j++)
	{
		bit[j] += ((arr[i]>>j)&1);
	}
	//接下來就是確定單獨出現數
	for(j = 0; j < 32; j++)
	{
		if(bit[j]%3 != 0)
		{
			num |= (1<<j); 
		}
	}

	return num;
}

int main()
{
	int arr[] = {2,3,2,4,2,4,4};
	int sz = sizeof(arr)/sizeof(arr[0]);

	int num = FindOnce_Number(arr,sz);
	printf("出現一次的數是:> %d\n",num);

	system("pause");
	return 0;
}
@結果: @既然已經講了這麼多,那就再擴充套件一道題祝個興,完美結尾! 題目(3)<百度面試題>:
在一個數組中,其他元素都是成對出現,只有兩個數字只出現了一次,找出這兩個數;
/* 分析:先舉出一個數組:{2,3,4,1,2,4};
先試試前面兩道題的解法,會發現,直接異或整個陣列,一下是解決不了的,那統計二進位制1的個數呢?
又不行,因為有兩個單獨出現的數! 是不是覺得很有挑戰性啊! 再仔細想想,既然和第一道題那麼類似
解法會不會類似呢?先把二進位制列出來: 2:0010
3:0011  
4:0100 
1:0001 
2:0010 
4:0100  整個陣列的異或結果:0010 看著二進位制,是不是在想如果可以將這兩個單獨的數分開來該多好!就可以用第一種方法求解了。 那麼想了
就試試,第一步肯定是將那兩個單獨的數分開存到兩個陣列中,這是本題的難點!!!
到這就不得不提醒你再回憶一下異或了,如果我們將整個陣列異或的話,最後的結果是單獨出項的那兩個數異或的
最終結果! 最終異或結果(過程就不列了):0010    那麼第二位是1,則說明兩個單獨出現的數的第二位不同;
那麼我們可以根據這個條件對陣列進行分組;第二位為1的為一組,第二位為0的為一組;然後每一組進行異或,
得到的兩個結果就是單獨出現的倆個數!(注意:我舉得例子比較簡單,如果最終異或結果有多個1,我們只根據
第一個出現的1的位進行分組); 程式碼實現如下:
*/
#include<stdio.h>
#include<stdlib.h>

void FindOnce_Number(int arr[], int sz)
{
	int i = 0;
	int j = 0;
	int num1 = 0;
	int num2 = 0;

	int tmp = 0;

	for(i = 0; i < sz; i++)
		tmp ^= arr[i];//整個陣列的異或結果既是兩個單獨數異或結果;
	for(i = 0; i < 32; i++)
	{
		if(((tmp>>i)&1) == 1)
			break;//找到第一個為1的位,並記住;
	}
	for(j = 0; j < sz; j++)
	{
		if(((arr[j]>>i)&1) == 0)//第i位為0的為一組全部異或;
			num1 ^= arr[j];
		else
			num2 ^= arr[j];//剩下的則是第i位為1的,為一組全部異或;
	}
	//最後的結果就是num1 和 num2;
	printf("單獨出現的兩個數分別是:%d 和 %d\n",num1,num2);
}

int main()
{
	int arr[] = {2,3,4,1,2,4};
	int sz = sizeof(arr)/sizeof(arr[0]);

	FindOnce_Number(arr,sz);

	system("pause");
	return 0;
}
@結果: 還可以吧! 這一篇結束!