藍橋杯——查詢的妙趣

Meteor發表於2023-01-05

一、查詢

1.1 遞迴式二分查詢

  • 作為查詢的必學演算法,二分查詢大家一定不陌生,透過前面我們所學的遞迴,那麼我們繼續強化遞迴思想,將二分查詢轉換成遞迴的方式。
  • 任何迴圈都能改成遞迴,遞迴也可以改成任何迴圈。

演算法思想:

  • 全範圍內二分查詢
    • 等價於三個子問題

      • 左邊找(遞迴):縮小範圍,可用遞迴,並且重複

      • 中間比

      • 右邊找(遞迴):縮小範圍,可用遞迴,並且重複

注意:

  • 左查詢和右查詢只選其中一個

  • 遞迴如果畫圖就會發現,其實是一個類似樹的樣子,有線性的,有二分的,三分的...

  • 二分查詢就像下面這樣,但是每一次都會捨去一半,這也是二分查詢效率高的原因

     /**
     * 遞迴式二分查詢
     * @param arr 陣列
     * @param low 左指標
     * @param high 右指標
     * @param value 查詢值
     * @return
     */
    public static int binarySearch(int[] arr, int low, int high, int value) {
        // 遞迴三步走:3. 找出口
        if(low > high) return -1;
        int mid = low + (high - low)/2;
        if(value < arr[mid]) {
            // 找重複、找變化:對左半部分進行二分查詢,最後返回的是我們查詢的結果
            return binarySearch(arr,low, mid-1, value);
        }
        else if(value > arr[mid]) {
            // 找重複、找變化:對右半部分進行二分查詢,最後返回的是我們查詢的結果
            return binarySearch(arr,mid+1, high, value);
        }
        else return mid;
    }

1.2 旋轉陣列最小數字

把一個陣列最開始的若干個元素搬到陣列的末尾,我們稱之為陣列的旋轉。輸入一個遞增排序的陣列的一個旋轉,輸出旋轉陣列的最小元素。例如陣列{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該陣列的最小值為1.

  • 最小值一定在無序的那邊,並且在最大值的右側,因為我們題中給的就是有序遞增序列。
  • 看到有序遞增序列的字眼,我們直接就能想到二分查詢。
  • 此題也是二分查詢的一種變形
	public static int ef(int[] arr) {
        int low = 0;
        int high = arr.length - 1;
        // 沒有翻轉的情況
        if(arr[low] < arr[high]) return arr[low];
        while (low <= high) {
            int mid = low + ((high - low) >> 1);
            if (arr[mid] >= arr[low]) {
                // 中間值大於左邊開始元素,則左邊是有序的,最小值一定藏在右邊
                low = mid;
            } else {
                high = mid;
            }
        }
        // 因為最小值一定在右側
        return arr[high];
    }

1.3 在有空字串中的有序字串查詢

有個排序後的字串陣列,其中散佈著一些空字串,編寫一個方法,找出給定字串(肯定不是空字串)的索引。

  • 這個題就不畫圖了,非常簡單,就是當我們中間mid取到空字串時候,我們移動mid指標,直到不指向空為止。
     public static int indexOf(String[] arr, String p) {
        int begin = 0;
        int end = arr.length - 1;
        while(begin <= end) {
            int mid = begin + ((end - begin) >> 1);
            while(arr[mid].equals("")) {
                mid++;
            }
            if(arr[mid].compareTo(p) > 0) {
                end = mid - 1;
            }else if(arr[mid].compareTo(p) < 0) {
                begin = mid + 1;
            }else {
                return mid;
            }
        }
        return -1;
    }

1.4 找出最長連續遞增子序列

(1,9,2,5,7,3,4,6,8,0)中最長的遞增子序列為(3,4,6,8)

  • 這個題非常的經典,我們可以採用雙指標策略,也稱之為滑動視窗演算法。
  • 模擬一個視窗進行滑動,最後視窗內的區域就是我們想要的答案。

	public static int zczxl(int[] arr) {
        // 用於存放結果
        int temp = 0;
        for (int i = 0; i < arr.length; i++) {
            // 滑動視窗右指標
            int right = i+1;
            int count = 0;
            // 1.當右指標掃描到最後 或者 符合遞增條件時
            while(right < arr.length && arr[right] > arr[i]) {
                // 2.符合條件,我們讓右指標繼續移動,擴大我們的視窗,直到移動到不符合條件為止
                right++;
                i++;
                count++;
            }
            // 3.更新結果,取最優解,也是最大值。
            temp = temp < count+1 ? count+1 : temp;
            // 4.我們透過更新i,也就是重新整理了左指標,讓左側視窗右移
        }
        return temp;
    }

二、 夢迴遞迴

2.1 小白上樓梯

小白正在上樓梯,樓梯有n階臺階,小白一次可以上1階,2階或者3階,實現一個方法,計算小白有多少種走完樓梯的方式。

  • 我們仍然再練習其它演算法的同時,不忘記我們的遞迴訓練。

  • 和斐波那契數列很相似,不過變成了遞迴三分支的

  • 我們透過逆推的方式,可以判斷最後上臺階只有三種模式,一種是差一步、第二種差兩個臺階、第三種差三個臺階

  • 將這三種情況子問題我們透過遞迴求出後,彙總即可。

	public static int slt(int n) {
        // 3. 找出口
        if(n == 0) return 0;
        if(n == 1) return 1;
        if(n == 2) return 2;
        if(n == 3) return 4;
        // 1.找重複
        // 2.找變化
        return slt(n-1) + slt(n-2) + slt(n-3);
    }

2.2 設計一個高效的求a的n次冪的演算法

解法一:O(n)

  • 這種解法正常人都能想出來
	/**
     * a的n次冪
     * @param a
     * @param n
     * @return
     */
    public static int pow1(int a, int n) {
        int res = 1;
        for (int i = 0; i < n; i++) {
            res *= a;
        }
        return res;
    }

解法二:
既然解法一是O(n),那麼我們如果想要再次最佳化演算法的時間,必然是logN級別的

  • 81 = 3^2 * 3^2 = 3^4
  • 所以我們先透過階乘求其一部分值,最後透過遞迴解決另一部分值,讓二者相乘就是我們的答案!
  • 還是非常的應用了:遞迴自己幹一部分,另一部分交給別人的思想!
    public static int pow(int a, int n) {
        int res = a;
        int ex = 1;
        if(n == 0) return 1;
        // 透過階層,我們先進行乘一部分
        while((ex<<1) <= n) {
            res *= res;
            ex <<= 1;
        }
        return res * pow(a,n-ex);
    }

三、結尾

  • 對於藍橋杯查詢知識內容就總結這麼多,若想深入學習等待後續更新。
  • 我將會繼續更新關於藍橋杯方向的學習知識,感興趣的小夥伴可以關注一下。
  • 文章寫得比較走心,用了很長時間,絕對不是copy過來的!
  • 尊重每一位學習知識的人,同時也尊重每一位分享知識的人。
  • ?你的點贊與關注,是我努力前行的無限動力。?