ROSALIND|Binary Search (BINS)
給定長度為 n 的嚴格遞增數列,求指定數字在此數列的位置。
Given: Two positive integers $n \leq 10^5$ and $m \leq 10^5$, a sorted array $A[1..n]$ of integers from $-10^5$ to $10^5$, and a list of $m$ integers $-10^5 \leq k_1, k_2, \ldots, k_m \leq 10^5$.
Return: For each $k_i$, output an index $1 \leq j \leq n$ such that $A[j] = k_i$, or “$-1$” if there is no such index.
(https://rosalind.info/problems/bins/)
題目要求我們在數列中尋找特定數字的位置。在沒有額外資訊的情況下,只能遍歷整個數列,所以搜尋時間與數列長度成正比。
反之,額外資訊能幫助我們判斷不可能存在目標數字的位置,減少搜尋時間。舉例來說,如果已知位置及數字的雜湊關係,便能在固定時間內找到目標數字。
如果數列已經過排序,則可以採用二分搜尋。簡言之,搜尋時先比較目標與數列中央的數字,若兩者不相等,則排除不可能包含目標的另一半數列。每一輪搜尋都排除一半的數列,直到找到目標或窮盡剩餘的數列。
二分搜尋可以用迴圈或遞迴實作,關鍵是用 left
和 right
紀錄待搜尋範圍,並且在比較目標與中央數字後更新此範圍。
兩種寫法類似,但我比較習慣迴圈寫法。畢竟每輪搜尋的操作只有比較數字大小,即使沒用遞迴,程式碼還是很好懂。另外,雖然在這題目的尺度內不是問題,遞迴有深度限制,呼叫副程式的次數達到極限會報錯。
1 | # 以迴圈實作 |
最後討論幾個練習時想過的問題。Rosalind 雖然有提供數列的長度,不過 Python list len()
的時間複雜度為 $O(1)$(可參考 Cost of len() function),可以取代數列長度資訊。
另外,在這個寫法裡如果數列長度為偶數,會以數列中央那對數字裡較小者為代表,即程式碼這段: mid = (left + right) // 2
。不過其實用較大的數字當代表也可以,mid = (left + right + 1) // 2
。
因為每次比對都會逐步更新待搜尋的範圍(left + 1
以及 right -1
),即使分割時沒有像切蛋糕一樣精準或是含有重複的數字,後續也不會漏掉實際存在的目標數字。