二分查找專(zhuān)題 - 基礎(chǔ)篇

二分查找 - 基礎(chǔ)篇

前言

從一個(gè)有序的數(shù)組中,找到某元素的值,通常思路就是二分查找。二分查找是一個(gè)常考的知識(shí)點(diǎn)。同時(shí),它也是非常容易出錯(cuò)的一道面試題。左右指針的位置,取值,比較是大于還是大于等于。里面細(xì)節(jié)很多。死記硬背往往容易出錯(cuò),只有真正理解思路和多多練習(xí),才能掌握不出錯(cuò)的”二分算法“。

本篇文章是二分查找的入門(mén)篇。將會(huì)介紹最傳統(tǒng),最容易理解與書(shū)寫(xiě)的二分算法。并介紹四種二分查找的進(jìn)階問(wèn)題。在理解本文的基礎(chǔ)上,后續(xù)文章將會(huì)再分享二分的各種變形和其他模板。

原題:在有序數(shù)組中查找定值

思路很簡(jiǎn)單,利用數(shù)組有序的特性,每次將數(shù)組二分,拿中間元素和目標(biāo)值比較。中間元素和目標(biāo)值相等,直接返回下標(biāo)索引。中間元素比目標(biāo)值小,則去右區(qū)間繼續(xù)二分查找,否則去左區(qū)間二分查找。

代碼如下,并不復(fù)雜,但有幾個(gè)需要注意的細(xì)節(jié):

  1. 循環(huán)條件

    比較大小使用 <= 小于等于號(hào)

  2. 防止死循環(huán)

    更新low,high指針?lè)謩e取值mid + 1 和 mid -1,注意這里的+1 和 -1,可以讓我們不用考慮死循環(huán)以及左中點(diǎn),右中點(diǎn)情況。假如我們使用high = mid,當(dāng)low = high的時(shí)候,就有可能進(jìn)入high = mid的分支邏輯中,導(dǎo)致無(wú)限死循環(huán)。

  3. 注意溢出

    在取中間值時(shí),很多人常用mid = (left + right) / 2的形式,當(dāng)left與right數(shù)值的加和較大時(shí) ,是有可能溢出Int的取值范圍的??梢圆捎?code>mid = left + (right - left) / 2的形式,結(jié)果是相同的。在JDK1.8中,采用(low + high) >>> 1,>>>是無(wú)符號(hào)右移,高位自動(dòng)補(bǔ)0,所以當(dāng)low + high溢出時(shí)變成負(fù)數(shù),無(wú)符號(hào)右移一位又變成了正數(shù)。

  public int bsearch(int[] a, int n, int value) {
        int low = 0;
        int high = n - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            if (a[mid] == value) {
                return mid;
            } else if (a[mid] < value) {
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }
        return -1;
    }

變形一:查找第一個(gè)等于給定值的元素

與原題區(qū)別在于,當(dāng)a[mid] == value時(shí),還需要確認(rèn)是不是第一個(gè)值等于給定值的元素。

當(dāng)遍歷到數(shù)組第一個(gè)數(shù),或者左邊值不同時(shí),必定是第一個(gè),直接返回(此處同時(shí)做了溢出校驗(yàn))

當(dāng)不是第一個(gè)元素時(shí),從右到左收縮區(qū)間,繼續(xù)二分查找。

  /**
     * 變形一:
     * 存在重復(fù)元素,查找第一個(gè)等于給定值的元素
     */
    public int bsearch1(int[] a, int n, int value) {
        int low = 0;
        int high = n - 1;
        while (low <= high) {
            int mid = (low + high) >>> 1;
            if (a[mid] > value) {
                high = mid - 1;
            } else if (a[mid] < value) {
                low = mid + 1;
            } else {
                //此處區(qū)別在于,當(dāng)a[mid]等于需要的值時(shí),還需要確認(rèn)是不是第一個(gè)值等于給定值的元素.當(dāng)遍歷到數(shù)組第一個(gè)數(shù),或者左邊值不同時(shí),必定是第一個(gè),直接返回
                if ((mid == 0) || (a[mid - 1] != value)) {
                    return mid;
                } else {
                    high = mid - 1;
                }
            }
        }
        return -1;
    }

變形二:查找最后一個(gè)等于給定值的元素

類(lèi)比變形一,變形一查找的第一個(gè),此處查找的最后一個(gè)元素。即需要確認(rèn)是不是最后一個(gè)值等于給定值的元素。

當(dāng)遍歷到數(shù)組最后一位,或者右邊元素值不等時(shí),必定是最后一個(gè),返回下標(biāo)。

如果不是最后一個(gè)元素,從左到右收縮區(qū)間,繼續(xù)二分查找。

   /**
     * 變形二:
     * 查找最后一個(gè)值等于給定值的元素
     */
    public int bsearch2(int[] a, int n, int value) {
        int low = 0;
        int high = n - 1;
        while (low <= high) {
            int mid = (low + high) >>> 1;
            if (a[mid] > value) {
                high = mid - 1;
            } else if (a[mid] < value) {
                low = mid + 1;
            } else {
                //此處區(qū)別在于,當(dāng)a[mid]等于需要的值時(shí),還需要確認(rèn)是不是最后一個(gè)值等于給定值的元素.當(dāng)遍歷到數(shù)組最后一位,或者右邊值不同時(shí),符合要求直接返回
                if ((mid == n - 1) || (a[mid + 1] != value)) {
                    return mid;
                } else {
                    low = mid + 1;
                }
            }
        }
        return -1;
    }

變形三:查找第一個(gè)大于等于給定值的元素

在變形一基礎(chǔ)上,將條件從查找一個(gè)等于定值的元素 改成 第一個(gè)大于等于定值的元素,增加了一個(gè)大于的判斷條件。則代碼如下:

  /**,
     * 變形三:
     * 查找第一個(gè)大于等于給定值的元素
     * 如序列:3,4,6,7,19 查找第一個(gè)大于5的元素,即為6
     * 第一個(gè)大于給定值,則說(shuō)明上一個(gè)小于給定值,依次判斷
     */
    public int bsearch3(int[] a, int n, int value) {
        int low = 0;
        int high = n - 1;
        while (low <= high) {
            int mid = (low + high) >>> 1;
            if (a[mid] >= value) {
                if ((mid == 0) || (a[mid - 1] < value)) {
                    return mid;
                } else {
                    high = mid - 1;
                }
            } else {
                low = mid + 1;
            }
        }
        return -1;
    }

變形四:查找最后一個(gè)小于等于給定值的元素

類(lèi)比上面三題,此處不再贅述。

  /**
     * 變形四:
     * 查找最后一個(gè)小于等于給定值的元素、
     * 如:3,5,6,8,9,10 最后一個(gè)小于7的元素是6
     */
    public int bsearch4(int[] a, int n, int value) {
        int low = 0;
        int high = n - 1;
        while (low <= high) {
            int mid = (low + high) >>> 1;
            if (a[mid] > value) {
                high = mid - 1;
            } else {
                if ((mid == n - 1) || (a[mid + 1] > value)) {
                    return mid;
                } else {
                    low = mid + 1;
                }
            }
        }
        return -1;
    }

Git代碼地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容