在日常面試中,經常遇到有的候選人甚至連二分都寫不對。你可能會說,不就是個二分嗎,有啥難的?其實,不要小看二分哦,能完全寫對的真的不多。
首先我們先看最樸素的二分寫法:
public int binarySearch(int[] a, int target) {
int left = 0;
int right = a.length;
while (left < right) {
int mid = ((right - left) >> 1) + left;
if (a[mid] == target) {
return mid;
} else if (a[mid] > target) {
right = mid;
} else {
left = mid + 1;
}
}
return -1;
}
對於上面這種二分實現,請大家思考以下幾個問題:
right的初始化可否改成:right = a.length – 1;
while迴圈的條件應該是left < right 還是 left <= right
右半查詢應該是left = mid + 1 還是 left = mid
左半查詢應該是right = mid 還是 right = mid -1
首先我們先不管第一個問題,暫且認為我們就把right初始化為right = a.length;
那麼來到第二個問題,為什麼不能是<=號。因為我們right的取值是n,如果我們while退出條件是<=,那麼當leftrightn的時候,是仍會進入到迴圈體的,然後迴圈體內訪問陣列的操作就會發生陣列越界。
第三個問題,寫成left = mid會有問題嗎?會有。假設讓left = k,而right = k + 1的時候,此時mid = (k + k + 1)/ 2,因為有整形向下取整的問題,算出來的mid仍然等於k。所以此時讓left = mid的話,二分範圍是沒有發生縮小的,此時就會造成死迴圈。
第四個問題,寫成right = mid – 1 會有問題嗎?會有。假設讓left = k,而right = k + 2的時候,此時mid = k + 1;如果此時mid > target,說明此時應該進行左半查詢:right = mid – 1 = k,此時有left == right,然而又有我們的迴圈退出條件是while (left < right),所以就退出二分了,那麼此時left == right 這種情況就漏查了。
理解了後三個問題,我們再回過頭來看第一個問題。也就是說當你決定給right初始化為 n 還是 n – 1 的時候,後三個問題應該選哪個就已經確定下來了。那麼,當right初始化為n-1的時候,二分又怎麼寫呢?
public int binarySearch(int[] a, int target) {
int left = 0;
int right = a.length - 1;
while (left <= right) {
int mid = ((right - left) >> 1) + left;
if (a[mid] == target) {
return mid;
} else if (a[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
ok,在這種寫法下,我們繼續看待上面幾個問題。
第二個問題:while迴圈的條件應該是left < right 還是 left <= right?如果此時while迴圈條件是<,那麼就會出現 left == n – 1 == right的時候,無法進入迴圈體,這個位置的資料就不會去查詢了,所以有問題
第三個問題同上,不重複了
第四個問題:左半查詢應該是right = mid 還是 right = mid -1?如果我們此時寫出 right = mid。假設當left == right時,此時target仍然大於a[left] 與 a[right],所以走到了右半查詢,但是right = mid,此時就會造成二分搜尋的區間並沒有變化,while進入了死迴圈。正確的是right = mid -1,使得 left <= right 條件不再成立,進而退出迴圈。
授之魚不如授之以漁,二分靠死記硬背多痛苦,這四處細節一處都不能錯。但是學會了以上分析的辦法,大家在現場寫二分的時候只需要多花一點點時間就能自己分析去每個細節應該如何確定。