很多人對於二分法的理解比較片面,之前碰到一個題目,從一個先升序後降序的數列中,比如 1 2 3 7 4 3 2 中運用二分法去查詢一個給定的元素,很多人說根本不能二分,因為沒有排序。其實 這道題完全可以使用二分查詢進行解答, 如果你覺得不可以的話,很可能對二分法理解還比較片面。 這裡以另外一個更加有趣(至少我認為)的例子來講解一下二分法。
題目
面試題: 有1000個一模一樣的瓶子,其中有1瓶是毒藥,老鼠喝了有毒的會在24h之後死亡。求最少需要多少老鼠才能在24h裡找到有毒的那瓶。
解法
這道題的解法有很多,今天我們來聊下用二分法來解這道題。 這道題似乎和我們看的的常見的二分法有很大的區別,但是仔細想一下, 二分法本質是將問題的規模縮小到原有的一半,帶著這樣的思想我們再來看一下。類似的,三分法就是將問題規模縮小為原來的1/3.
我們先對1000個瓶子進行編號,從1-1000這樣子。 不過我們不是通過我們大家平時生活中使用的十進位制,而是使用再計算機中使用的二進位制, 同時讓大家感受一下二進位制的魅力。
為了方便講解,我們假設不是1000個瓶子,而是4個。
我們來編一下號:
00 // #1
01 // #2
10 // #3
11 // #4
複製程式碼
我們的目標是找到哪個瓶子有毒,換句話說我們目標是找到有毒瓶子的編號,再換句話說我們的目標是 找到有毒瓶子的3個bit分別是什麼,是0還是1.
比如有毒的是3號瓶子,那麼我們就是想確認第一個bit是0,第二個bit是1,第三個bit是1,即011,轉化為10進位制就是3號。
那麼如何確定每一個bit是什麼呢? 回想一下,我們手上有老鼠,老鼠有兩個state,alive 或者 died,這就是我們擁有的全部。
接下來我們逐一對瓶子進行分組,分組的依據就是每一個bit的值。
比如:
// 00 01 #g1:1 第一個bit是0
// 10 11 #g1:2 第一個bit是1
// 00 10 #g2:1 第二個bit是0
// 01 11 #g2:2 第二個bit是1
複製程式碼
我們來找第一個老鼠#1 來喝g:1:1, 如果他死了,那麼毒就在這一組,也就是說毒的第一個bit是0,否則是1
我們來找第二個老鼠#2 來喝g:2:1, 如果他死了,那麼毒就在這一組,也就是說毒的第二個bit是0,否則是1
所以我們可以看出, 兩隻老鼠就搞定了,我們按照這個思路,可以推到出1000個瓶子只需要10個瓶子, 即 log2 1000, 2的10次方是1024,因此10個老鼠夠了,如果1025個瓶子的話,就需要11個老鼠了。
如果你仔細思考的話,不難看出,我們是在用老鼠喝了水之後的反應(生或死)來進行判斷每一個bit的數字,不管生死,我們總能得出這個bit的值,是0還是1. 因此每使用一隻老鼠我們都將問題規模縮小為原來的1/2. 這是典型的二分法。
這是最優解麼
是的,這是最優解,如果你願意用嚴格的數學來證明的話,你可以試一下數學歸納法。 如果你想感性的判斷一下的話,可以繼續往下讀。
什麼是最優解? 最優解就是要讓未知世界無機可乘,也就是說在最壞的情況下得到最優(現實世界都是未知的)。上面的例子,不管小老鼠是生還是死,我們都可以將問題規模縮小到1/2. 也就是說最壞的情況就是最好的情況,也就是說沒有最壞情況。
那麼我們是否可以將問題規模縮小的1/3 或者更小呢?
我們可以三分麼
簡單來說,不可以, 因為老鼠只有兩種observable state, 即alive, died. 假如我們有10個小球,其中有一個是異常的,其他9個都是一樣的,我們怎麼才能通過最少的稱量來確定是哪一個異常,是重還是輕? 這個時候我們就可以使用三分法了,為什麼?因為天平有三個state, 平衡,左傾,右傾,使得我們”有可能“ 將問題規模縮小為1/3, 事實上,確實可以實現將問題規模縮小到1/3。
我會在之後的文章中進行講解小球的問題最優策略, 並解釋為什麼這是最優策略。
Bonus
基於比較的排序都無法逃脫nlogn時間複雜度的命運,這是為什麼?能否利用本篇文章的思想進行解釋?