1. 程式人生 > >資料結構之——找到無序陣列中排序後相鄰元素差值的最大值

資料結構之——找到無序陣列中排序後相鄰元素差值的最大值

/**
 * 有一個整形陣列A,請設計一個複雜度為O(n)的演算法,算出排序後相鄰兩數的最大差值。
給定一個int陣列A和A的大小n,請返回最大的差值。保證陣列元素多於1個。
 * @author Administrator
 *

 */

今天在牛客網刷題時,遇到這麼一道題,聽完知識點講解後,稍微有些不理解,後來自己又仔細想了想。現在整理下思路。

這個題中,陣列是亂序的,所以很容易想到的解法就是先選擇一種排序演算法,然後依次迭代找到最小值即可。但是這樣的複雜度就依賴與排序演算法的複雜度了。若是採用計數排序的話,有些不現實,因為最大值和最小值之間可能會差別比較大,造成空間浪費。

其實說這麼多,就是為了說明這個題還有一種解法,就是類似桶排序的演算法。

對於 陣列中 n 個數,我們用 n+1 個桶來裝,而且在構造這n+1個桶的時候,,需要一點技巧。我們把 用 n (注意:此處用 n ,而不是 n+1 )來除 max{A} - min{A} ,這樣就把陣列中 從最大值到最小值 分成了 n 份,這兒稍微有點繞。可以參考下面的圖片進行理解。一開始還以為老師講錯了,後來debug 了下,發現還真是這麼回事。利用下面的程式碼計算每個元素的桶號,這樣計算出來的最大元素的桶號正好是最後一桶。 num 表示的桶的數目, 即 n+1  ,value表示當前要入桶的值。

//該方法用來計算對應元素的應該放入的桶號
	private int bucket(long value, long num, long max, long min) {
//		int tag = (int) ((value-min)/((max-min)/(num-1)));  應該避免這種連續除法的表示式,因為每次處的時候都會舍,到最後結果會相差很大
		int tag = (int) ((value-min)*(num-1)/(max-min));
		return tag;
	}

可以這麼理解,我們把這些元素想象成人,元素值的大小就是人的身高,然後根據每個人的身高,決定某個人應該站在哪一級臺階上。站臺階的規則是這樣的:最矮的只能站在地上,最高的則站在最高處。因為一共有 n 個數, 但是卻有 n+1 個臺階,所以,中間必然有個臺階是沒“人” 站的,當然,可能還有另外的臺階也沒站人,但是,此時至少有一個是沒有站人的,至少有一個,抽屜原理。,而且,而且,(預警,問題的關鍵點來了)相鄰臺階上的 “人” 的身高的差,不會大於中間隔著空閒臺階的人的身高差!道理很簡單,反證一下,如果相鄰臺階上人的身高差,超過一個臺階高度的話,那麼這兩個人站上的臺階就不可能是挨著的。所以,最大的身高差,只能出現在與空閒臺階挨著的左右兩個非空閒臺階中。

好,問題說到這兒,說了一半。另外一個是:我們沒有必要將每個元素到放到對應的臺階上,每個臺階上只留下兩個值就好,一個最大值,一個最小值。開始的時候,臺階上的最大值和最小值可以都初始化為零。然後更新呢就好。

好,那麼問題到這兒就可以做個了結了。兩個相鄰元素的最大的差值,就是,空閒臺階的後一個緊挨著的非空閒臺階上的最小值     減去     空間臺階上前一個緊挨著的非空閒臺階上的最大值。注意,不一定只有一個空閒臺階,所以要迴圈迭代,更新最大的gap。

上圖:

只是一個簡單的例子供理解使用。

其實這個題可以沒必要非得想象成臺階,但是為了說明問題,不防這麼思考下。因為人的思維總是不擅長抽象的東西,而更喜歡具象的東西。我們何不利用這個特點呢?其實,資料結構中很多地方都把抽象的東西具象化了。比如,根據最小堆排序,最小堆無非就是我們對陣列中的元素進行來來回回交換的一個準則而已,如果寫程式的過程中,頭腦中不想象出一個堆的話,恐怕會很難寫吧。

最後還想囉嗦一下。我寫部落格主要是為了給自己看的,方便自己後來複習。當哪天發現知識點有些遺忘的時候,開啟自己整理過的部落格讀讀,應該很快就能重拾記憶。但是,我在翻閱別人部落格的時候,發現有些人的部落格好像就是要專門寫給別人看的一樣,字裡行間透漏著 我給你講。。。。。你應該。。。。怎樣怎樣,,什麼什麼 你懂嗎?。。。 啊,這個這個你竟然都不懂?。。。。。。。一副好為人師的樣子。自己永遠都是那麼牛逼樣子。誰都有一個從認識到理解的過程啊,你也不是一下子就那麼牛逼的不是?看的我好生厭惡。藉此吐槽一下。

package Sort;
/**
 * 有一個整形陣列A,請設計一個複雜度為O(n)的演算法,算出排序後相鄰兩數的最大差值。
	給定一個int陣列A和A的大小n,請返回最大的差值。保證陣列元素多於1個。
 * @author Administrator
 *
 */
public class Gap {
	public int maxGap(int[] A, int n) {
		
		if( A==null || A.length<2){   //感覺這些程式碼總是忘寫,其實還是很有用的,只能說明思維不嚴謹啊
			return 0;
		}
		int max = A[0];
		int min = A[0];
		//找到最大值和最小值
		for(int i=0;i<n;i++){
			max = Math.max(max, A[i]);
			min = Math.min(min,A[i]);
		}
		if(max==min){ 			//感覺這些程式碼總是忘寫,其實還是很有用的
			return 0;
		}
		int num =  n+1;//共用n+1個桶來存放,其中大的元素必定放在最後一個桶中
		int[] mins = new int[num];//存放每個桶中的最小值,在向桶中放入元素時,更新此值
		int[] maxs = new int[num];
		boolean[] hasNum = new boolean[num]; //用來標記對應下標的桶中是否為空
		
		//向桶中放入元素,每次放入元素時,根據原桶中的狀態來更新
		for(int i=0;i<n;i++){
			int tag = bucket(A[i],num,max,min);
			mins[tag] = hasNum[tag]?Math.min(mins[tag], A[i]):A[i];
			maxs[tag] = hasNum[tag]?Math.max(maxs[tag], A[i]):A[i];
			hasNum[tag] = true;
		}

//		放好元素後,開始計算最大的差值
		int gap = 0;
		int preMax = 0;//記錄前一個桶中的最大值
		int aftMin = 0;//記錄後一個桶中的最小值 
		int i = 0;
		while(i<num){   //在n+1 個桶中迴圈更新 gap的值,每次只在空桶兩側更新,因為首先n個元素,n+1個桶,保證了肯定有空桶,並且最大的差值肯定出現在空桶的兩側非空桶中元素的差值中
			while(i<num && hasNum[i]){   //尋找第一個空桶,因為n+1 個桶存放 n 個元素,肯定會有空桶存在,而最大值,就會出現在空桶中相鄰兩個桶中元素的差值
				i++;
			}
			if(i>=num)
				break;
			preMax = maxs[--i];
			i++;
			while(i<num && !hasNum[i]){ //在上次找到了空桶的基礎上,繼續迴圈,空桶之後的下一個非空的桶,此處因為我們把最大的元素放到了最後一個桶中,所以不用擔找不到下一個非空的桶
				i++;
			}
			aftMin = mins[i];   
			gap = gap>(aftMin  - preMax)?gap:(aftMin  - preMax); 
		}
		return gap;
    }
//該方法用來計算對應元素的應該放入的桶號
	private int bucket(long value, long num, long max, long min) {
//		int tag = (int) ((value-min)/((max-min)/(num-1)));  應該避免這種連續除法的表示式,因為每次處的時候都會舍,到最後結果會相差很大
		int tag = (int) ((value-min)*(num-1)/(max-min));
		return tag;
	}
	public static void main(String[] args) {
		Gap g = new Gap();
		//int[] a = {9590,5695,4776,5450,9128,2219,4782,5023,3685,54,2688,4716,8175,1012,3079,5574,6336,3755,5328,2925,3825,3443,9253,7427,3809,9360,8809,5885,8944,2048,8170,1285,1223,2186,2969,5342,5076,911,391,1950,2480,1264,9795,45,1763,9169,7526,6225,5571,5805,9468,9383,6885,1576,9810,6544,853,9628,175,4595,5025,8688,9239,975,5370,673,9435,5559,205,744,2146,2597,987,7727,3116,5843,1575,6252,2245,4205,3481,3600,1910,8912,3824,1333,8392,6311,3504,8379,9636,244,5835,1896,4181,5452,617,4850,6326,2381,4524,5502,9970,9919,8138,69,5831,7087,416,8547,5568,4658,7418,2703,837,9223,7926,5703,3623,7347,8297,5270,9026,1913,6588,4362,6278,2129,422,6819,3828,7056,3926,8088,7424,7823,9408,2836,4629,8770,5793,662,269,9958,9569,9321,5095,9075,9883,8858,79,7148,6446,2854,8157,9279,6063,696,236,7060,8070,3431,854,4811,858,2501,9900,6987,5021,6246,8057,3198,2847,2758,4540,5268,7924,6729,8661,9045,8118,6669,5603,5585,9753,8098,2712,6487,2500,7702,7103,9492,7662,7623,671,7095,2214,7512,4991,1712,5238,1874,8203,6442,6785,158,1343,2574,415,6316,8487,3159,2151,5176,3250,1572,7555,188,3098,5595,7337,5693,2928,5179,1251,8417,1235,7807,1733,4618,1664,5007,1589,857,4440,4041,4652,6515,1284,6564,8916,9961,7111,4749,6903,9085,4064,6868,1399,9619,4977,4505,5226,7264,5149,9909,5065,1164,3057,9360,5084,336,4994,9715,7446,8395,7324,6319,9460,464,2259,2183,2365,9115,7768,8529,4445,2349,3414,5595,2768,9367,9366,8874,2052,8875,8481,8528,8631,874,3344,2434,2454,4231,5793,5388,9261,6075,4236,904,3433,7027,7065,3230,8830,6011,830,9467,8512,3098,6858,7226,228,4202,7216,2414,9676,8528,348,7004,8965,7050,4045,5379,9432,6224,8699,3739,9822,1867,1486,9600,6690,930,2807,7584,691,8949,118,9720,1286,3489,5623,1596,8657,7902,63,5136,4851,1111,5665,4060,9291,5156,4845,5331,8544,6089,6574,5618,4406,6850,939,5107,9179,2240,1964,7938,1021,6793,8417,1102,2565,8379,703,7768,8183,5518,9335,3839,1258,8463,589,5209,2419,9296,3570,9351,2648,4734,6006,306,5720,1112,8299,1619,68,1986,279,7750,9680,9980,5407,7364,1275,4493,3118,4026,2670,2072,978,9299,3314,2361,4415,9950,8859,5163,817,4150,7022,891,4788,7405,9288,5527,3923};
		int[] a = {3,1,2,4,9,8,7};
		int c = g.maxGap(a, 7);
		System.out.println(c);
	}
}


廢話多了,上程式碼吧: