1. 程式人生 > >算法系列——二分查詢演算法及其變體總結

算法系列——二分查詢演算法及其變體總結

基礎二分查詢

基本二分查詢的程式程式碼如下所示

        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;
    }
}