算法系列——二分查詢演算法及其變體總結
基礎二分查詢
基本二分查詢的程式程式碼如下所示
int left = 0;
int right = nums.length - 1;
int mid;
//條件必須是 <=
while (left <= right) {
mid = (left + right) / 2;
//target 在左邊
if (target < nums[mid])
right = mid - 1;
//target 在右邊
else if (target > nums[mid])
left = mid + 1;
else
return mid;
}
return -1;
基礎二分查詢只能找到指定target在序列中的位置,假如target有重複值,二分查詢只能返回其中某個target的位置,這個位置並不確定,主要跟target值的起始位置和重複序列的長度有關係。
二分查詢變體
在基礎二分查詢的程式碼中,加入在while迴圈中不執行返回退出操作,判斷條件做稍微改動,其實只是去掉了返回操作,只剩一個if-else判斷。
//target 在左邊
if (target < nums[mid])
right = mid - 1;
//target 在右邊
else
left = mid + 1;
那麼最終 left,right關係是 right+1=left.
舉個栗子,例如 nums={ 1,2,3,3,3,3,4,5 } target=3
當 判斷條件為if (target <= nums[mid]) ... else
時,最終位置狀態為
1,2(right),3(left),3,3,3, 4,5
right left的位置卡在target序列的左邊界。
當判斷條件為 if (target < nums[mid]) ... else
1,2,3,3,3,3(right),4(left),5
right left的位置卡在target序列的右邊界。
那麼假如target不存在呢?比如 nums={1,2,4,5} target=3
當 判斷條件為if (target <= nums[mid]) ... else
時,最終位置狀態為
1,2(right),4(left),5
當判斷條件為 if (target < nums[mid]) ... else
時,最終位置狀態為
1,2(right),4(left),5
從以上的分析中可以看到,left,right 總是停留在target元素的附近,並且還有一定的規律。
其實根據不同的條件下left,right位置資訊,我們可以利用二分查詢還能能解決以下6個問題:
返回第一個=target的元素位置,此時判斷符號<= 要返回left;
返回第一個>=target的元素位置,此時判斷符號<= 要返回left;
返回第一個>target的元素位置,此時判斷符號<要返回left;
返回最後一個=target的元素位置,此時判斷符號<,要返回right;
返回最後一個<=target的元素位置,此時判斷符號位<,返回right;
返回最後一個< target的元素位置,此時判斷符號位<=,返回right。
以上問題都可以套用如下二分查詢的結構:
····
//條件必須是 <=
while (left <= right) {
mid = left + (right-left) / 2;
//target 在左半部分
if (target ➀ nums[mid])
right = mid - 1;
//target 在右邊部分
else if (target > nums[mid])
left = mid + 1;
}
...
return ➁;
➀ 所在位置的比較符號要麼是< 要麼是<=
➁ 位置要麼是 left要麼是right.
下表給出六種情況下➀➁處的取值。
問題 | ➀處符號 | ➁處值 |
---|---|---|
返回第一個=target | <= | left |
返回第一個>=target的元素位置 | <= | left |
返回第一個>target的元素位置 | < | left |
返回最後一個=target的元素位置 | < | right |
返回最後一個<=target的元素位置 | < | right |
返回最後一個< target的元素位置 | <= | right |
如何記憶
那麼如果我們碰到以上6種情況,但沒表可以查怎麼辦,其實也比較好解決,
首先程式結構要按以上給出的結構來寫,那麼根據問題,確定出問題在哪一種種判斷條件下才能取得結果,是< 還是 <= ,其次確定出 應該返回left 還是right.
比如要求 第一個大於> target的元素位置,那right left 必定卡在 target序列的右邊界,此時判斷條件只能是 <,並且 應該返回 left。
程式實現
下面給出所有情況的具體程式實現,實際運用時要弄懂問題要求,再按照具體情況給出不同的解答。
public class Solution {
/**
* 基礎二分查詢
*
* @param nums
* @param target
* @return
*/
public int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int mid;
//條件必須是 <=
while (left <= right) {
mid = left + (right-left) / 2;
//target 在左邊
if (target > nums[mid])
right = mid - 1;
//target 在右邊
else if (target < nums[mid])
left = mid + 1;
else
return mid;
}
return -1;
}
//----------以下是二分查詢的6種變體情況-------------
// nums={ 1,2(right),3(left),3,3,3(right) 4(left),5 } target=3
// nums={1,2(right),4(left),5} target=3
//1. first =(left(<=)) >=(left(<=)) >(left(<))
//2. last =(right(<)) <=(right(<)) <(right(<=))
/**
* 找第一個大於target的元素位置 符號必須是<
*
* @param nums
* @param target
* @return
*/
public int findFirstGreater(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int mid;
//條件必須是 <=
while (left <= right) {
mid = left + (right-left) / 2;
//target 在左邊
if (target < nums[mid])
right = mid - 1;
//target 在右邊
else
left = mid + 1;
}
return left;
}
/**
* 查詢第一個大於等於target的元素位置 符號是<= 保證 left有指向target的機會
*
* @param nums
* @param target
* @return
*/
public int findFirstGreaterEqual(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int mid;
//條件必須是 <=
while (left <= right) {
mid = left + (right-left) / 2;
//target 在左邊 條件是 target> nums[mid]
if (target <= nums[mid])
right = mid - 1;
//target 在右邊
else
left = mid + 1;
}
return left;
}
/**
* 查詢第一個等於target的元素位置 符號是<=
*
* @param nums
* @param target
* @return
*/
public int findFirstEqual(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int mid;
//條件必須是 <=
while (left <= right) {
mid = left + (right-left) / 2;
//target 在左邊
if (target <= nums[mid])
right = mid - 1;
//target 在右邊
else
left = mid + 1;
}
//判斷邊界
if (left < nums.length && nums[left] == target)
return left;
return -1;
}
/**
* 查詢最後一個=target的元素位置 符號是<
*
* @param nums
* @param target
* @return
*/
public int findLastEqual(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int mid;
//條件必須是 <=
while (left <= right) {
mid = left + (right-left) / 2;
//target 在左邊
if (target < nums[mid])
right = mid - 1;
//target 在右邊
else
left = mid + 1;
}
if (right >= 0 && nums[right] == target)
return right;
return -1;
}
/**
* 查詢最後一個<target的元素位置 <=
*
* @param nums
* @param target
* @return
*/
public int findLastLess(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int mid;
//條件必須是 <=
while (left <= right) {
mid = left + (right-left) / 2;
//target 在左邊
if (target <= nums[mid])
right = mid - 1;
//target 在右邊
else
left = mid + 1;
}
return right;
}
/**
* 查詢最後一個<=target的元素位置 <
*
* @param nums
* @param target
* @return
*/
public int findLastLessEqual(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int mid;
//條件必須是 <=
while (left <= right) {
mid = left + (right-left) / 2;
//target 在左邊
if (target < nums[mid])
right = mid - 1;
//target 在右邊
else
left = mid + 1;
}
return right;
}
}