天天看點

查找算法之二分查找查找算法之二分查找

查找算法之二分查找

簡單介紹二分查找

二分查找是一種效率較高的查找方法,要求線性表必須采用順序存儲結構,其時間複雜度為O(log2n)

  • 二分查找的條件:必須是有序數組
  • 二分查找的思想——将目标值和有序數組的中間值進行比較:

​ 當目标值=有序數組中間值時,查找成功;

​ 當目标值<有序數組中間值時,則目标值隻可能在左側,改變右邊界;

​ 當目标值>有序數組中間值時,則目标值隻可能在右側,改變左邊界;

當左右邊界相等時,區間縮成一個點,循環結束,若此時目标值與有序數組中間值仍然不相等,那麼目标值不在有序數組中。

例1、在有序數組中查找目标值

題目

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

查找算法之二分查找查找算法之二分查找

來源:力扣(LeetCode)

連結:https://leetcode-cn.com/problems/binary-search

解題思路

鎖定關鍵條件——“升序數組”,那麼在比較nums[i]與目标值target時想到優先采用二分查找法來尋找目标值。

首先定義left和right分為為用于比較的數組的左邊界和右邊界。

每一次都先找到數組範圍的中點mid,比較nums[mid]和target的大小:

  • 若nums[mid] = target,則mid為要尋找的小标,傳回mid
  • 若nums[mid] > target,則target隻可能在左側,此時右邊界right改變
  • 若nums[mid] < target,則target隻可能在右側,此時左邊界left改變

每次進行二分查找都會将查找範圍縮小一半,當left>right時結束查找,即target不在數組中,此時傳回-1。

代碼

class Solution {
    public int search(int[] nums, int target) {
        /*
        定義一個變量left存儲最左邊(最小)的值的下标
        同理right存儲最右邊(最大)的值的小标
         */
        //由于數組下标是從0開始的,是以right的定義需要-1
       int left = 0,right = nums.length - 1;
       while (left <= right){
           /*
           mid為數組最中間的值的下标
           mid的定義可以舉例去思考更容易了解
            */
           int mid = (right - left)/2 +left;
           if (nums[mid] == target){
               return mid;
           }else if(nums[mid] > target){
               /*
               由于是升序數組,是以當中間值大于目标值時,
               說明目标值隻可能在左側,是以右邊界right發生改變
                */
               right = mid -1;
           }else {
               /*
               同理:
               由于是升序數組,是以當中間值小于目标值時,
               說明目标值隻可能在右側,是以左邊界left發生改變
                */
               left = mid +1;
           }
       }
        return -1;
    }
}
           

例2、找出導緻之後所有版本出錯的第一個錯誤版本

題目

你是産品經理,目前正在帶領一個團隊開發新的産品。不幸的是,你的産品的最新版本沒有通過品質檢測。由于每個版本都是基于之前的版本開發的,是以錯誤的版本之後的所有版本都是錯的。

假設你有 n 個版本 [1, 2, …, n],你想找出導緻之後所有版本出錯的第一個錯誤的版本。

你可以通過調用 bool isBadVersion(version) 接口來判斷版本号 version 是否在單元測試中出錯。實作一個函數來查找第一個錯誤的版本。你應該盡量減少對調用 API 的次數。

查找算法之二分查找查找算法之二分查找

來源:力扣(LeetCode)

連結:https://leetcode-cn.com/problems/first-bad-version

解題思路

根據題意,當某個版本為正确版本,則該版本之前所有版本都是正确版本;同理當某個版本為錯誤版本,則該版本之後所有版本都是錯誤版本。抓住這一特性,結合題意要求”盡量減少調用API的次數“,是以我們優先想到二分查找法。

确定左右邊界之後,根據左右邊界計算中間版本,并判斷是否為正确版本:

  • 若該版本是正确版本,則第一個錯誤版本肯定在右側,是以左邊界改變
  • 若該版本是錯誤版本,則第一個錯誤要麼是該版本要麼在左側,是以右邊界改變

代碼

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        //沒有用數組存儲,是以開始邊界初值為1
        int left = 1,right = n;
        while (left < right){
            int mid = (right - left)/2 +left;
            //調用了力扣的接口isBadVersion
            if (isBadVersion(mid)){
                /*
                如果中間版本是錯誤版本,則第一個錯誤要麼
                是該版本要麼在左側(具體的還是需要進一步的判斷),
                是以右邊界改變
                 */
                //注意right沒有定義為mid-1,是因為錯誤版本可能就是中間版本
                right = mid;
            }else {
                /*
                若中間版本是正确版本,那麼第一個錯誤版本肯定在右側,
                是以左邊界改變
                 */
                left = mid +1;
            }
        }
        //此時left=right,區間為一個點,也就是答案
        return right;
    }
}
           

例3、在一個排序數組中搜尋目标值的插入位置

題目

給定一個排序數組和一個目标值,在數組中找到目标值,并傳回其索引。如果目标值不存在于數組中,傳回它将會被按順序插入的位置。

請必須使用時間複雜度為 O(log n) 的算法。

查找算法之二分查找查找算法之二分查找

來源:力扣(LeetCode)

連結:https://leetcode-cn.com/problems/search-insert-position

解題思路

同樣的先确定左右邊界,根據左右邊界計算中間下标mid;對nums[mid]和target進行比較:

  • 若相等,則傳回下标mid;
  • 若nums[mid]<target,則左邊界left右移;
  • 若nums[mid]>target,則右邊界right左移;

假設插入位置為pos,那麼nums[pos-1]<target<=nums[pos](因為target有可能比所有的值都大,此時要插入在最右側,是以存在等于關系),題意可以簡化為”在一個有序數組中找第一個大于等于target的下标“,是以同樣可以使用二分查找法。

代碼

class Solution {
    public int searchInsert(int[] nums, int target) {
        int n = nums.length;
        /*
        ans初值設定為數組長度可以省略邊界條件的判斷,
        以為存在一種情況是target大于數組中的所有數,
        此時需要插入到數組長度的位置
         */
        int left = 0,right = n-1,ans  = n;
        /*
        當你插入進去後,目标值也就成為了nums[pos],
        是以nums[pos]<=target<nums[pos+1]也是可以的
         */
        while (left <= right){
            int mid =((right - left) >>1) +left;
            if (target <= nums[mid]){
                ans = mid;
                right = mid -1;
            }else {
                left = mid +1;
            }
        }
        return ans;
    }
}
           

注意:

java中<<、>>、>>>三種移位運算符:

查找算法之二分查找查找算法之二分查找