由於準備找工,最近也用零星的時間做了一些面試題,這道題屬於偏難的那種了,之所以說它偏難,主要是因爲按常規思維不容易得到最優解,更重要的是,此題的轉化不容易想到。做過編程之美的同學都能瞭解到,很多題目都需要一種“轉化”的思想,把問題轉化爲另一種已知的熟悉的問題去求解,這種思路在《如何解題》也是被提到了很多次,不過實施起來真的不容易。 一 題目描述: 有一個整數數組,請求出兩兩之差絕對值最小的值,只要求出最小值即可,不要求求出是哪兩個數。 二 常規思路: 求解此題的尋常思路是什麼?觀察題目我注意到後面強調不要求求出兩個數,那麼最最簡單的O(n^2)的算法顯然做了很多無用功。嗯,好,既然這個辦法不行想想其他的。對於數組也就是序列之類的題,有一種很常用的思路那就是預處理。這道題目貌似是可以的。 首先,對數組進行排序,這個可以在O(n*logn)時間之類解決,然後,有了這個預處理,就會想到,絕對值之差最小值肯定只能發生在預處理的數組之後的相鄰的元素上,這個是很顯然的事實。那麼我們便可以循環一遍數組,記下兩兩之間絕對值的最小值,那麼所求得到值便是解答,總的時間複雜度是O(n*logn)。仔細想想這種方法,很明顯,排序減小了我們所需要搜尋的解空間,從而達到了減小時間複雜度的目的。不過這個解法仍然不能讓人滿意,因爲我們還是浪費時間求出了最終的兩個元素,而題目不要求,所以,這肯定不是最優解。 三 轉化的思想 再仔細觀察題目,我們可以猜到,最優解應該是隻求出最小值而不求出具體的元素的,那麼該怎麼做呢?我們可能能想到用輔助數組,但是卻很難想到怎麼做這個輔助。其實這道題我一直在思考如何通過常規的思維去想到這個最優解,不過我當時沒有想出來,而這纔是我寫這篇博客的原因,即促使我瞭解並對這種思路印象深刻,不過這可能只適用於解這題或者類似能讓我聯想到這種方法的題,這背後更一般的思維(可以叫做轉化,但是還可以更具體些)我還沒有想到,希望想到的同學聯繫我!。 好了,本題要做的輔助數組是這樣一個數組,設它爲Bn.原來題目中給定的數組是An,則Bn等於: B1 = A1 - A2; B2 = A2 - A3; B3 = A3 - A4; ...... Bn-1 = An-1 - An. 注意,Bn的長度是n-1,正好比An要小一個。聰明的同學看到這個輔助數組,立馬就能猜到原因了,因爲這樣做的話,我們能夠把這道看似無從下手求出最優解的問題轉化爲求Bn的絕對值最小的最長連續子序列和,因爲Bn的連續子序列和便是An任意兩數之差(注意,由於題目要求的是絕對值最小,所以求出A1-A2等效於得出A2-A1),例如: A2 - A5 = B2 + B3 + B4 = A2 - A3 + A3 - A4 + A4 - A5 = A2 - A5 實際上,任何Ai - Aj(i<j) = sigma(k=i -> k=j-1)(k) 這樣的話,我們就成功把問題轉化爲了連續子序列問題,不過和我們以前做的最大或最小連續子序列還不完全相同,此處是絕對值最小。那麼怎麼樣的值可能是絕對值最小呢?正數最小或者負數最大,也就是說在數軸上離0更近的數其絕對值更小,基於此我們可以得到如下的方法。 和原來求最大連續子序列和一樣,要用數學歸納法思考,我們直接看歸納基礎, 歸納基礎: 假設已知B1..Bk的絕對值最小連續的連續子序列和是Min(Bk) 我們利用這個求解B(k+1),加入B(k+1)後有可能比Min(Bk)小的只可能是以B(k+1)結尾的絕對值最小的連續子序列和,如果把這個和Min(Bk)比較就可以知道是否需要更新Min(Bk)。所以,我們加強這個歸納基礎。 更強的歸納基礎: 假設已知B1..Bk的絕對值最小連續的連續子序列和Min(Bk),以及以Bk結尾的絕對值最小連續子序列和Suffix(Bk) 有了這個歸納,我們可以去想如何維護這個Suffix(Bk),目標是使的Suffix(B(k+1))仍然是以B(k+1)結尾的最小連續子序列和。如果按照求最小和的思路,那便是隻要Suffix(Bk)是正數便置它爲0,因爲如果它是正數,那麼在後續求Suffix(B(k+1))時就肯定比用0要更大,因爲正數會使得整個值變大,而0不會。同樣的道理,我們只要使得求Suffix的時候比直接置0更小即可,否則我們可以直接把Suffix(B(k+1))置0以獲得更小值。由於我們求的是絕對值最小,直接按最小值的思路是不行的,因爲可能某個Suffix是暫時求得一個很小的負數,下次加上某個正數會使得它成爲很小的正數,所以不能以正數負數作定論而要以與0的距離。所以我們應該採取比較符號的方法,如果當前suffix和下一個數的符號相反,那麼可以繼續相加以求得下一個suffix,因爲我們可以獲得絕對值更小的suffix;如果是同號,無論正負一定會比把當前suffix置0更糟糕,因爲這將使得下次的suffix在數軸上離0更遠。所以我們維護Suffix的公式如下: Suffix(B(k+1)) = Suffix(B(k)) + B(k+1), if (Suffix(B(k))*B(k+1)) < 0 Suffix(B(k+1)) = 0, if (Suffix(B(k))*B(k+1)) ) > 0 這樣我們一直歸納下去,便可以求得最終的Min(Bn),即可求得解。整個的時間複雜度是O(n),空間複雜度是O(n)。 四 程序 程序如下:
1 //TestAlgo.cpp : Defines the entry point for the console application. 2 // 3 4 5 #include"stdafx.h" 6 7 #include<iostream> 8 #include<cmath> 9 usingnamespacestd; 10 11 intGetMinAbsoluteSubsequence(intB[],intnLen) 12 { 13 intnGlobal=INT_MAX; 14 15 intnSuffix=0; 16 17 for(inti=0; i<nLen; i++) 18 { 19 nSuffix+=B[i]; 20 21 if(abs(nSuffix)<abs(nGlobal)) 22 { 23 nGlobal=nSuffix; 24 } 25 26 27 if(i+1<nLen) 28 { 29 if(nSuffix*B[i+1]>0) 30 nSuffix=0; 31 } 32 } 33 34 returnabs(nGlobal); 35 } 36 37 intGetMinAbsoluteDiff(intA[],intnLen) 38 { 39 //create aid array 40 int*b=newint[nLen-1]; 41 memset(b,0,sizeof(b[0])*(nLen-1)); 42 for(inti=0; i<nLen-1; i++) 43 { 44 b[i]=A[i]-A[i+1]; 45 } 46 47 returnGetMinAbsoluteSubsequence(b,nLen-1); 48 } 49 50 int_tmain(intargc, _TCHAR*argv[]) 51 { 52 intA[]={1,20,200,16,13}; 53 intnLen=5; 54 55 cout<<GetMinAbsoluteDiff(A,nLen); 56 57 getchar(); 58 return0; 59 }
五 總結 整個思路過程便是這樣,總的來說,這類題目還是很有思考價值的,至少讓我們體會到了各種美,也能深刻領會轉化的意義。 |
【轉】[面試題] 求數組兩兩之差絕對值最小的值
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.