《劍指offer》Java學習錄:查找和排序(面試題8:旋轉數組)

查找和排序

查找

查找和排序時程序設計中常用的算法,查找相對簡單,大致有順序查找、二分查找、哈希查找和二叉樹查找,其中二分查找是大多數面試官都會考察的內容。這幾個查找都各有特點:

  • 順序查找:是最普通的查找方式,雖然常用,但並不推薦。
  • 二分查找:用在排序或者部分排序數組中查找一個數字或者統計某個數字出現的次數。
  • 哈希表:可以讓我們在O(1)O_{(1)}的時間查找某一元素,是效率最高的查找方式。缺點是需要額外的空間來實現哈希表。
  • 二叉樹查找:樹查找算法對應的數據結構是二叉搜索樹,利用搜索樹有序結構,查找可以適用於折半查找。

排序

在面試中常見的排序有:插入排序、冒泡排序、歸併排序、快速排序等,我們不僅要知道這些排序的寫法,還要知道不同算法的優劣,如空間消耗、平均時間複雜度和最差時間複雜度等方面去比較他們的優點。

面試題8:旋轉數組的最小數字

題目

把一個數組最開始的若干個元素搬到數組的末尾,我們稱之爲數組的旋轉。輸入一個遞增排序的數組的一個旋轉,輸出旋轉數組的最小元素。例如數組「3, 4, 5, 1, 2」爲「1, 2, 3, 4, 5」的一個旋轉,該數組的最小值爲1。

分析

該題最簡單直觀的解法是從頭到尾遍歷一遍,就能知道最小的元素。這種思路的時間複雜度是O(n)O_{(n)} 但卻沒有用到遞增這一特性。

我們注意到旋轉之後的數組實際上可以劃分爲兩個排序的字數組,前面字數組的元素都大於或等於後面字數組的元素,最小元素剛好是這兩個字數組的分界線。考慮到給糊的數組在一定程度上是排序的,因此我們可以利用二分查找的思路來尋找這個最小元素。

  1. 用兩個指針分別只想第一個元素和最後一個元素。
  2. 找到數組中間的元素,如果中間元素位於前面的遞增字數組,那麼它應該大於或者等於第一個指針指向的元素,此時數組中最小的元素應該位於該中間元素後面。我們就可以把第一個指針指向該中間元素,這樣可以縮小尋找的範圍。移動之後的第一個指針認爲與前面遞增字數組之中。
  3. 如果中間元素位於後面的遞增字數組,那麼它應該小雨或者等於第二個指針指向的元素,此時數組中最小的元素應該位於該中間元素後面。我們可以把第一個指針指向該中間元素,這樣可以縮小尋找的範圍。移動後的第二個指針任然位於後面的遞增數組中。
  4. 不管移動第一個還是第二個指針,查找範圍都會縮小到原來的一半。接下來就是循環了。
  5. 最終第一個指針將只想前面子數組的最後一個元素,而第二個指針會只想後面子數組的第一個元素,即它們最終會指向兩個相鄰的元素,而第二個指針剛好就是最小的元素,這是循環的結束條件。

解:Java

public static void main(String[] args) {
  int[] arr = {3, 4, 5, 1, 2, 3};
  System.out.println(min(arr));
}

public static int min(int[] arr) {
  if (arr == null || arr.length == 0) {
    return -1;
  }

  int left = 0;
  int right = arr.length - 1;
  int mid = 0;
  while (arr[left] >= arr[right]) {
    if (right - left == 1) {
      mid = right;
      break;
    }
    mid = (right - left) / 2 + left;
    if (arr[mid] >= arr[left]) {
      left = mid;
    } else if (arr[mid] <= arr[right]) {
      right = mid;
    }
  }
  return arr[mid];
}

這樣就完了麼

題目中提到,在旋轉數組中,由於是把遞增排序數組前面的若干個數字搬到數組的後面,因此第一個數字總是大於或者等於最後一個數字。再來考慮一下特殊情況:如果是把排序數組的前面0個元素搬到最後面,即排序數組本身,這任然是數組的一個旋轉,我們的代碼需要支持這種情況。此時,數組中的第一個數字就是最小的數字,可以直接返回。這就是一開始mid = 0的原因。

在來看一個特殊情況:數組「1, 0, 1, 1, 1」和數組「1, 1, 1, 0, 1」都可以看成是遞增排序數組「0, 1, 1, 1, 1」的旋轉。

這兩種情況下,第一個指針和第二個指針指向的數字都是1, 並且兩個指針中間的數字也是1, 三個數字相同時,我們無法確定中間數字是位於前面的字數組還是後面的字數組中,也就無通過移動指針來縮小查找範圍了。所以不得不採用順序查找的方法。

   public static void main(String[] args) {
        int[] arr = {1, 0, 1, 1, 1, 1};
        System.out.println(min(arr));
    }

    public static int min(int[] arr) {
        if (arr == null || arr.length == 0) {
            return -1;
        }

        int left = 0;
        int right = arr.length - 1;
        int mid = 0;
        while (arr[left] >= arr[right]) {
            if (right - left == 1) {
                mid = right;
                break;
            }
            mid = (right - left) / 2 + left;
            if (arr[right] == arr[left] && arr[mid] == arr[right]) {
                // 三個數相等時,只能遍歷
                return minInOrder(arr);
            } else if (arr[mid] >= arr[left]) {
                left = mid;
            } else if (arr[mid] <= arr[right]) {
                right = mid;
            }
        }
        return arr[mid];
    }

    private static int minInOrder(int[] arr) {
        if (arr == null || arr.length == 0) {
            return -1;
        }
        int temp = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (temp > arr[i]) {
                temp = arr[i];
            }
        }
        return temp;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章