最近在公司要計算一下我們所有用戶排列中相連兩個人的年齡差的到最大差值以統計公司用戶年齡層。
我們公司的客戶是數量很大,所以普通的排序求差值或者快排算法其實很難滿足要求。
一個簡單的排序算法求解如下:
def stepIn(dataInput) dataLen = dataInput.length diff = nil dataResule = {} for i in 0...dataLen-1 for n in i+1...dataLen diff2 = dataInput[n] - dataInput[i] if diff == nil or diff < diff2 diff = diff2 dataResule["#{i},#{n}"] = diff end end end rIdxs = dataResule.sort_by {|k,v| -v} [0][0].split ',' return [dataInput[rIdxs[0].to_i], dataInput[rIdxs[1].to_i]] end
上面的代碼已經經過了優化,在每次循環后,保留了之前計算的差值的結果,下面的循環中小於這個差值的索引值就被拋棄了,這樣的一個好處是可以減少最后sort時花費的時間。
假如保留所有兩數之間的差值,假設使用冒泡排序,輸入數組長度是m,排序算法復雜度是O(n2),而這個n會達到(m+1)*m/2,所以總的算法復雜度就成了O(n4)。
而在循環中預處理之后,最后參與排序的元素個數最大不會超過m,總的時間復雜度還是O(n2)。
其實這只是針對最后的sort而言,而這個程序真正的耗時在上面的嵌套循環,這里的復雜度不管有沒有優化,其實都是一樣的O(n2),下面sort的消費可以忽略不計。
這是一種比較直觀的解法了,事實證明,沒有經過斟酌的想法都是不完善的。這個算法最后用到實際運算時需要消耗的資源和時間是成指數上升的。
后來仔細想了一下,其實完全可以使用動態規划的思想去解決這個問題。
動態規划的思想通常可以分成下面幾部:
- 給問題分階段
- 確定每個階段的狀態
- 確定相鄰階段的之間的遞推關系(也就是找出從前一個階段轉化到后一個階段的條件)
上面的問題很容易可以分出三個階段
- 開始階段,將數組中開頭的兩個元素作為最大,最小值記錄在結果數組中,[A,B]
- 過程階段,將后面的數與前面的數比較,比如將C與B比較,並將符合條件的值替換結果數組
- 結束階段,當游標抵達數組最后一個元素時,跳出循環。
而這幾個狀態之間的轉移條件在上面已有了說明,主要在第二個階段,哪些條件能決定替換結果數組,這些條件稱為決策
- 游標所指的數大於結果數組中的最大值,比如后面有C,那么結果數組就變成[A,C]
- 游標所指的數小於結果數組中的最小值,那么它就有可能在后面替換結果數組中的最小值,例如后面出現了A-1,這個時候不能立刻替換掉A,需要找個臨時變量將A-1保存下來。
- 游標所指的數與臨時最小值之差大於結果數組中兩數字之差。這個條件應該優先於決策2和決策1,一旦這個決策生效,將同時替換結果數組中的最大最小值,決策1和決策2在這個時候應該不生效。例如后面出現了D,那么結果數組就應該變成[A-1,D]。假如這個時候決策1優先生效,那么結果數組會變成[A,D],而臨時變量A-1將永遠沒有上位之日了。
有了上面的階段和決策之后,代碼就很容易實現了
1 def stepIn(list) 2 min = 0 # minimal index 3 max = 0 # maximal index 4 differ = 0 # max differ 5 minTmp = nil # temp minimal index 6 for i in 1...list.length 7 if minTmp != nil and list[i] - list[minTmp] > differ # if current index minus temp minimal index is bigger than differ, replace it 8 differ = list[i] - list[minTmp] # new differ 9 min = minTmp # new minimal index 10 max = i # new maximal index 11 elsif list[i] > list[max] # replace the maximal index 12 max = i # new maximal index 13 differ = list[i] - list[min] # new differ 14 elsif list[i] < list[min] and ( minTmp == nil or list[i] < list[minTmp] ) # replace the temp minimal index 15 minTmp = i # change temp minimal index 16 else 17 next 18 end 19 end 20 return [list[min], list[max]] 21 end
可以看出使用第二種算法的時間增長基本是線性的。
