零零散散學算法之找出數組中重複的數---總結篇

找出數組中重複的數

前序
        最近一直在看v_JULY_v的專欄,從中學到了很多關於算法方面的知識,也受到了很大的啓發。我相信喜歡算法的朋友,看過他的博文之後也會有這種想法。前段時間參加了一些面試,從他的專欄裏學到的算法給予了我不小的幫助,這讓我在面試的時候輕鬆了不少。他的博文我將會繼續關注和學習。

        好了,我們言歸正傳。關於找出數組中重複數的這個問題,網上已經有很多文章來闡述。不過我認爲各位對於這個問題考慮的情況不是很全面,於是自己就想把這個問題完善一下。

        問題描述:給你一個數組A[N],該數組中存放着N個自然數,要求是找出數組中重複的那個數。

        對於這個問題,我認爲可以分兩種情況討論。
        情況一:這N個數的大小在1---N-1之間。如A[5]:2、1、3、1、4。
        情況二:這N個數的大小是任意的,這N個數可能在1---N-1這個區間裏,也可能不在。如A[7]:1、3、5、7、9、9、11。
        其實情況一就是情況二的一個特例罷了。不過適合情況一的解法卻不一定適合情況二,但是適合情況二的則一定適合情況一。

        對於第一種情況,也就是這N個數的大小在1---N-1之間,有很多種方法可以實現。在這,我只講只適合此種情況卻不適合情況二的解法。
        解法一:我最先想到的就是利用數學的方法,我叫它求和做差法。這種方法的思想是:聲明兩個變量TempSum1、TempSum2,利用一個簡單的for循環,即TempSum1=1+2+...N,TempSum2=A[0]+A[1]+...A[N-1],於是通過N-(TempSum1-TempSum2)便可得到這個重複的數。實現代碼如下:
int RepeatNum(int *Array, int N)
{
	int i;
	int TempSum1 = 0, TempSum2 = 0;
	for(i = 0;i < N;i++)
	{
		TempSum1 = i + 1;
		TempSum2+ = Array[i];
	}
	return N-(TempSum1 - TempSum2) ;
}

        解法二:網上將此種解法稱之爲標誌數組法該解法的核心思想是:申請一個數組長度爲N-1的字符串數組Help[N-1],並將該數組的各項均初始爲’0‘,然後從頭開始遍歷數組A[N],取A[i]的值,並將在Help數組上相應的位置,即Help[A[i]-1] = '1',如果該位置已經是置過'1'的話,那麼該數就是那個重複的數。該方法的實現代碼如下:
int RepeatNum(int *Array, int N)
{
	int i;
	char Help[N-1];
	for(i = 0;i < N-1;i++)
	{
		Help[i] = '0';
	}
	for(i = 0;i < N;i++)
	{
		if(Help[A[i]-1] == '1')
			return A[i];
		else
			Help[A[i-1]] = '1';
	}
}

        解法三:固定偏移標誌法。這種方法是我在網上找的,該解法的核心思想是:利用A[N]本身中值和下標的關係來做標記,處理完成後再清楚標記即可。對於數組A[N],最大的數是N-1,若A[i] = M在某處出現時,將A[M]加一次N,做標記,當某處A[i] = K再次成立時,查看A[K],就可以知道K已經出現過。A[i]在程序中最大的時候是N-1+N=2*N-1,這不是超出範圍了嗎?不要緊,我們可將它作爲一個限制條件。實現代碼如下:
int RepeatNum(int *Array,int N)
{
	int temp=0;
	
	for(int i=0; i<N; i++)
	{
		if(Array[i] >= N)
			temp = Array[i]-N; // 該值重複了,因爲曾經加過一次了
		else 
			temp = Array[i];

		if(Array[temp] < N) 
		{
			Array[temp]+ = N; //做上標記
		}
		else 
		{    
			return temp; //有重複;
		}
	}
		return -1;//無重複
}

             好了,對於情況一的解法暫時就是這些。如果您還有更好的解法請提出來,分享一下。接下來我們開始討論情況二所對應的相關解法。

        情況二,即這N個數的大小是任意的。在這我給出三種解法,前兩種解法都是很基礎的。
        解法一:我們可以用兩個for循環搞定。很簡單,這就像學習最簡單的排序算法一樣。不過該算法的效率確實太低了,核心思想就不說了,直接看代碼吧!
int RepeatNum(int *Array,int N)
{
	int i, j;
	
	for(i = 0;i < N - 1;i++)
	{
		for(j = i + 1;j <= N - 1; j++)
		{
			if(Array[i] == Array[j])
			{
				return Array[j];
			}
		}
	}
}

            解法二:由於該數組中的元素很有可能是無序的,所以當你拿到這個問題時,解法一通常是先想到的。但是由於解法一的效率不是很好(時間複雜度爲O(N*N)),那我們怎麼提高呢?OK,我們來看看解法二。解法二的思想很簡單,就是先對數組做一次排序,讓數組成爲有序的,即從小到大或者從大到小。做排序的話,可供我們選擇的優秀的排序算法有很多,比如快速排序、堆排序、歸併排序等,而且這幾種排序算法的時間複雜度都是O(N*LgN),你可以任選一種。排序完成之後,數組A[N]變成有序數組,那麼從這個有序數組中找出那個重複的數,問題就好解決了,只需做一次循環就可以解決。具體做法是:從頭開始遍歷這個有序數組,當前一個數和後一個數相等時就是我們要找的那個重複的數,不相等時只需i++,比較一下組數,知道找出那個重複的數。對於此解法,時間複雜度爲O(N*LgN+N)。實現代碼如下:

int RepeatNum(int *OrderArray,int N)
{
    int i;
    for(i = 0;i < N;i++)
    {
        if(OrderArray[i] == OrderArray[i+1])
            return OrderArray[i];
    }
}

         上述兩種解法,我們應該很容易就能夠做到。那麼還有沒有更好的方法呢?當然有,接下來我們就開始介紹第三種方法。


          解法三:我認爲可以用哈希算法來解決這個問題。利用哈希算法,可以建立一種鍵值與真實值之間的對應關係。每一個真實值只有一個鍵值,但是每一個鍵值可以對應多個真實值,這樣可以再數組中很快的找到我們想要的數,而且利用哈希算法可以對該問題在時間複雜度上做出很好的優化。關於哈希算法及其原理,我在網上找了一些資料分享一下。


        哈希算法原理:http://wenwen.soso.com/z/q181569235.htm

        哈希算法:http://baike.soso.com/ShowLemma.e?sp=l7892037&ch=w.search.baike.unelite

        著名的哈希算法:http://blog.csdn.net/longronglin/article/details/1782678

        暴雪的哈希算法:http://opaque.blogbus.com/logs/30017835.html


          OK,以上就是對數組中找重複數的幾種解法。不過我相信對於這個問題的解法絕不止上述幾個,肯定還有更好的解法,歡迎大家分享,共同交流。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章