有N個數的數組,沒有順序。現在的問題是讓你在數組中找出兩個數,使得這兩個數的和盡可能的接近0。
想到的的方法是嘗試所有數對<xi,xj>的組合,之后找出其中和的絕對值最小的數對即可。但是這樣做的時間復雜度是O(N^2),有沒有更快一點的方法呢?
這里給出一個O(NlogN)時間復雜度的算法。
有一種比較直觀的做法。
對數組排好序之后。如果數字全部是正數,那么取最小的兩個數的和。如果數字全部是負數,則取最大的兩個數字的和。如果數字有正有負。那么我們必須枚舉每一個xi,之后用二分在數組中找小於等於-xi的最大值和大於等於-xi的最小值。能與xi構成和的絕對值最小的數字,肯定就是這兩個數字中的某一個。所有的xi都枚舉過之后,我們就可以找出和的絕對值最小值了。時間復雜度是O(NlogN)。
還有一種比較簡便的做法,但是證明有些復雜。
1:對數組中的數進行從小到大排序。
2:設置兩個游標,一個指向第一個數,另一個指向最后一個數,之后進行如下操作:把游標處的兩個數相加取絕對值,與記錄當前已經得到的最小絕對值比較,小則記錄。之后對游標處的數的絕對值進行比較,移動指向數字絕對值較小的游標(如果是左面的游標指向的數字絕對值小,則向右移動,否則右面的游標向左移動)。循環此步驟,直至找到最小絕對值0或者游標相遇。
舉個例子: 原始序列: -2 3 4 -7 9 5 排序后: -7 -2 3 4 5 9 尋找過程: res = MAX_INF; -7 -2 3 4 5 9 | | res = 2 -7 -2 3 4 5 9 | | res = 2 -7 -2 3 4 5 9 | | res = 2 -7 -2 3 4 5 9 | | res = 2 -7 -2 3 4 5 9 | | res = 1 -7 -2 3 4 5 9 | res = 1
雖然給出了這樣的算法,但是怎么證明該算法是正確的?以下的證明很繁瑣。
證明:
設排好序的序列為
x[1],x[2],......,x[i],x[i+1],......,x[j],x[j+1],......,x[n-1],x[n]
假設最優解為<x[i],x[j]>
x[1],x[2],......,x[i],x[i+1],......,x[j],x[j+1],......,x[n-1],x[n]
| |
如果算法是正確的,那么必須得證明算法執行過程中游標不會錯過最優解。
初始時,游標為<1, n>,顯然沒有錯過最優解。之后每步,都有一個游標移動。那么肯定有一個游標會先到達最優位置。例如左面的游標最先到達x[i]的位置,而另一個游標的位置為k>j
x[1],x[2],....,x[i],x[i+1],....,x[j],x[j+1],....x[k],....,x[n-1],x[n]
| |
下面分情況討論:
(1): x[i] > 0
這種情況下,顯然 x[j] > 0 , x[k] > 0,並且x[i]+x[k] > x[i]+x[j],那么把右面的游標向左移動會更優,並且不會錯過最優解。
(2): x[i] < 0,x[j] > 0
這種情況下又分3中小情況。
2.1: x[i]+x[j] > 0
這種情況下,x[i]+x[k] > 0 並且x[i]+x[j] < x[i]+x[k] (因為x[j]<x[k]),那么把右面的游標向左移動會更優,並且不會錯過最優解。
2.2: x[i]+x[j] < 0, x[i]+x[k] < 0
這種情況下,顯然|x[i]+x[k]| < |x[i]+x[j]|,推出<x[i],x[j]>不是最優解,這與假設相矛盾,所以在遇到最優解之前,這種情況不會出現。
2.3: x[i]+x[j] < 0, x[i]+x[k] > 0
這種情況下有|x[i]+x[j]| < x[i]+x[k]=>-x[i]-x[j]<x[i]+x[k]=>x[j]+x[k] > -2*x[i] => x[k] > -x[i] = |x[i]| (因為x[j] < -x[i])。也就是說,在這種情況下,我們會移動位置為k那個游標,而不會移動位置為i那個游標,所以不會錯過最優解。
(3): x[i] < 0, x[j] < 0
這種情況下又繼續分成兩種小情況。
3.1 x[k] < 0
這種情況下<x[i],x[j]>不是最優解,這與假設相矛盾,所以在遇到最優解之前這種情況不會出現。
3.2 x[k] > 0
這種情況下也分兩種小情況。
3.2.1 x[i]+x[k] < 0
|x[i]+x[j]| < |x[i]+x[k]| => -x[i]-x[j] < -x[i]-x[k] => x[j] > x[k],這與x[j] < x[k]相矛盾,因為x是排好序的數組。所以這種情況在遇到最優解之前不會出現。
3.2.2 x[i]+x[k] > 0
|x[i]+x[j]| < |x[i]+x[k]| => -x[i]-x[j] < x[i]+x[k] => x[k]+x[j] > -2*x[i]=> x[k] > -2*x[i]-x[j] => x[k] > |x[i]|。也就是說,在這種情況下,我們會移動位置為k那個游標,而不會移動位置為i那個游標,所以不會錯過最優解。
由對稱性可證明當右面的游標先到達x[j]時這種情況。
綜上所述可知,只要重復算法的過程,那么我們的游標肯定能夠到達最優解<i,j>的狀態,而我們采用的方法又是不斷記錄最小值,那么我們肯定能夠得到最優解。
三樓給出了一個非常簡便的方法:按照絕對值排序。仔細想想,其實就是我上面的方法的另一種形式。
比如,
-7 -2 3 4 5 9
按照3樓的方法,將會是 -2 3 4 5 7 9取相鄰兩數值差絕對值的最小值。如果我們按照絕對值由大到小排序,則序列為9 7 5 4 3 -2,那么這時取相鄰兩個數的絕對值等價於方法二的兩個游標。