二分查找算法的萬能公式(LeetCode35、704、1095)

二分查找常常用於有序數組的查找操作。當然如果是一個問題,數組的順序滿足特定條件(不僅僅是升序或者降序,也可以是先升後降或先降後升),就可以通過逐步排查,縮小問題的規模的方式找到,這種算法也是二分查找算法。

PS:不想看過程的,直接拉到萬能公式,即看即用!

兩種二分查找

第一種

在這裏插入圖片描述

第二種

在這裏插入圖片描述
在這裏插入圖片描述
從上面可以看出:

第二種方式有兩種邊界轉移方式:

  • left = mid + 1;right = mid; 這種不會產生死循環
  • left = mid-1;right = mid; 這種會產生死循環(循環條件一直成立),此時需要把mid向上取整。

思維導圖

在這裏插入圖片描述

萬能公式

int search(int[] nums, int target, int l, int r) {
	while (l < r) {
		int mid = l + (r - l) / 2;
		
		// if (符合條件) return mid;

		if (target在右區間) {
			l = mid + 1;
		} else {
			r = mid;
		}
	}
	if (符合條件) return l;
	else return -1;
}

適用於:
在這裏插入圖片描述

主要注意的幾個點:

  • while繼續循環的條件:left<right; 注意不要帶等於號,while退出循環的時候,一定是left==right,所以不需要額外的考慮用left還是right去判斷條件。
  • 取中位數的時候,防止left和right過大,以至於left+right超過int類型上限,可以用mid = left + (right - left) / 2;來求中位數。
  • 在while中,如果題目說明了沒有重複元素的話,那麼萬能公式中註釋的if條件可以去掉註釋,以便提前跳出條件,如果存在相同的元素讓輸出最左邊或者最右邊的元素,那麼這個if條件就不能加
  • 賦值left和right邊界的時候,需要注意left一定要先賦值,跟快排需要先從右邊一樣。

代碼示例(套公式示例)

LeetCode704

【題目】

給定一個 n 個元素有序的(升序)整型數組 nums 和一個目標值 target ,寫一個函數搜索 nums 中的 target,如果目標值存在返回下標,否則返回 -1。

【示例】

輸入: nums = [-1,0,3,5,9,12], target = 9
輸出: 4
解釋: 9 出現在 nums 中並且下標爲 4

【代碼】

public int search(int[] nums, int target) {
        if (nums==null || nums.length==0)
            return -1;

        int left,right,mid;

        left = 0;
        right = nums.length-1;

        while (left < right) {
            mid = left + (right - left) / 2;
            // if (nums[mid]==target)
            //     return mid;

            if (nums[mid]<target)
                left = mid+1;
            else
                right = mid;
        }

		// 由於條件可能不存在,所以需要額外判斷
        if (target==nums[left])
            return left;
        else return -1;
    }

LeetCode35

【題目】

給定一個排序數組和一個目標值,在數組中找到目標值,並返回其索引。如果目標值不存在於數組中,返回它將會被按順序插入的位置。

你可以假設數組中無重複元素。

【示例】

輸入: [1,3,5,6], 5
輸出: 2

輸入: [1,3,5,6], 2
輸出: 1

輸入: [1,3,5,6], 0
輸出: 0

輸入: [1,3,5,6], 7
輸出: 4

【代碼】

public int searchInsert(int[] nums, int target) {
        if (nums==null || nums.length==0)
            return 0;

        int left,right,mid;
        left = 0;
        right = nums.length-1;

		// 最大值的情況需要額外判斷
        if (target>nums[right]) return right+1;

        while (left < right) {
            mid = left + (right - left) / 2;
            // if (nums[mid]==target)
            //     return mid;

            if (nums[mid]<target)
                left = mid+1;
            else
                right = mid;
        }

		// 由於條件一定成立,所以直接輸出即可
        return left;
    }

LeetCode1095

【題目】

給你一個 山脈數組 mountainArr,請你返回能夠使得 mountainArr.get(index) 等於 target 最小 的下標 index 值。

如果不存在這樣的下標 index,就請返回 -1。

何爲山脈數組?如果數組 A 是一個山脈數組的話,那它滿足如下條件:

首先,A.length >= 3

其次,在 0 < i < A.length - 1 條件下,存在 i 使得:

  • A[0] < A[1] < … A[i-1] < A[i]
  • A[i] > A[i+1] > … > A[A.length - 1]

你將 不能直接訪問該山脈數組,必須通過 MountainArray 接口來獲取數據:

  • MountainArray.get(k) - 會返回數組中索引爲 k 的元素(下標從 0 開始)
  • MountainArray.length() - 會返回該數組的長度

【示例】

輸入:array = [1,2,3,4,5,3,1], target = 3
輸出:2
解釋:3 在數組中出現了兩次,下標分別爲 25,我們返回最小的下標 2。

輸入:array = [0,1,2,4,2,1], target = 3
輸出:-1
解釋:3 在數組中沒有出現,返回 -1

【思考】

  • 先找到山頂元素 mountaintop 所在的索引。

  • 在前有序且升序數組中找 target 所在的索引,如果找到了,就返回,如果沒有找到,就執行第 3 步;

  • 如果步驟 2 找不到,就在後有序且降序數組中找 target 所在的索引。

所以問題的關鍵就是如何在一個先升後降的數組中找到最高的一個。

	// 尋找峯頂的代碼
	public int getTop(MountainArray mountainArr) {
        int left,right,mid,midVal,midAf;

        left = 0;
        right = mountainArr.length()-1;

        while (left < right) {
            mid = left + (right - left) / 2;

            midVal = mountainArr.get(mid);
            midAf = mountainArr.get(mid+1);

            if (midAf > midVal)// 如果當前的數比右邊的數小,它一定不是山頂
                left = mid + 1;
            else
                right = mid;
        }
        return left;
    }

在這裏插入圖片描述

如果當前的數比右邊的數小,它一定不是山頂,又由於left和right的二分是mid在左區域(看上圖),所以最後跳出的時候,只有兩個元素,mid在左邊,left和right同時指向右邊,右邊就是那個峯頂。

【全部代碼】

/**
 * // This is MountainArray's API interface.
 * // You should not implement it, or speculate about its implementation
 * interface MountainArray {
 *     public int get(int index) {}
 *     public int length() {}
 * }
 */
 
class Solution {
    public int findInMountainArray(int target, MountainArray mountainArr) {
        if (mountainArr==null || mountainArr.length()==0)
            return -1;

        int l,top,leftIndex;
        top = getTop(mountainArr);
        l = 0;
        leftIndex = getAsc(mountainArr,target,l,top);
        if (leftIndex != -1)
            return leftIndex;
        else
            return getDesc(mountainArr,target,top,mountainArr.length()-1);

    }

    /**
     * 求得山峯的峯頂元素
     * */
    public int getTop(MountainArray mountainArr) {
        int left,right,mid,midVal,midAf;

        left = 0;
        right = mountainArr.length()-1;

        while (left < right) {
            mid = left + (right - left) / 2;

            midVal = mountainArr.get(mid);
            midAf = mountainArr.get(mid+1);

            if (midAf > midVal)
                left = mid + 1;
            else
                right = mid;
        }
        return left;
    }

    /**
     * 左邊升序求指定元素
     * */
    public int getAsc(MountainArray mountainArr, int target, int l ,int r) {
        while (l < r) {
            int mid = l + (r - l) / 2;
            if (mountainArr.get(mid) < target)
                l = mid + 1;
            else r = mid;
        }

        if (mountainArr.get(l) == target)
            return l;
        else return -1;
    }

    /**
     * 右邊降序求指定元素
     * */
    public int getDesc(MountainArray mountainArr, int target, int l ,int r) {
        while (l < r) {
            int mid = l + (r - l) / 2;
            if (mountainArr.get(mid) > target)
                l = mid + 1;
            else r = mid;
        }

        if (mountainArr.get(l) == target)
            return l;
        else return -1;
    }

}

// 用於本地測試,提交的時候可以不寫以下代碼
class MountainArray {
    ArrayList<Integer> table;
    public MountainArray() {
        table = new ArrayList();
    }
    public int get(int index) {
        return table.get(index);
    }
    public void add(int val) {
        table.add(val);
    }
    public void addAll(int[] a) {
        for (int i=0; i<a.length; i++) {
            table.add(a[i]);
        }
    }
    public int length() {
        return table.size();
    }
}

【Java 面試那點事】

這裏致力於分享 Java 面試路上的各種知識,無論是技術還是經驗,你需要的這裏都有!

這裏可以讓你【快速瞭解 Java 相關知識】,並且【短時間在面試方面有跨越式提升】

面試路上,你不孤單!
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章