基礎面試題 — 資料結構與演算法

Joelixy發表於2019-01-14

資料結構

資料結構是對實際問題中的資料元素及相互間的聯絡的抽象。一般用線性表來表示常用資料結構,線性表分為順序儲存的順序表和連式儲存的連結串列。

常用資料結構

在學習演算法之前,必須要了解一些常用資料結構的概念。

  • 棧:一種特殊串聯形式的抽象資料型別,可由連結串列或陣列實現,通過連結串列或陣列的棧頂(Top)指標對資料進行壓棧(Push)和出棧(Pop)操作,其特點是LIFO。

  • 佇列:先進先出(FIFO)的線性表,一般用連結串列和陣列來實現,只允許在後端(back or rear)插入,在前端(front)刪除。

  • 陣列:由相同元素的集合所組成的資料結構,儲存在一塊連續的記憶體單元,根據元素的索引可以計算出該元素對應的儲存地址。

  • 連結串列:由一連串節點組成,每個節點包含任意的例項資料和一個或兩個用來指向下一個/上一個節點位置的連結。

  • 樹:實現抽象資料型別的資料結構,如:二叉樹、霍夫曼樹。

  • 圖:表示物件與物件之間的關係,圖論的基本研究物件。

  • 堆:是電腦科學中一種特別的樹狀資料結構,也是一種特殊的二叉樹。

  • 雜湊表:根據鍵(key)直接訪問記憶體儲存位置的一種資料結構,通過計算一個關於鍵值的函式,將所需查詢的資料對映到表中的一個位置來訪問記錄,對映函式叫做雜湊函式,存放記錄的陣列叫雜湊表(雜湊函式和雜湊衝突是實現雜湊表最重要的兩個環節)。

演算法

所謂演算法,就是解決問題方案的準確而完整的描述。

運算操作

為了完成各種運算,計算機提供了一套最基本的功能操作:

  1. 算術運算:加、減、乘、除;
  2. 邏輯運算:與、或、非;
  3. 比較運算:大於,小於,等於,不等於;
  4. 資料轉送:輸入、輸出,賦值。

演算法分析的重要指標

  1. 時間複雜度:指演算法執行所需要的時間,即演算法的時間代價。
  2. 空間複雜度:對一個演算法在執行過程中所需要的輔助儲存空間大小的度量。

演算法面試題

介紹完理論,可以開始實踐了,以下演算法題目有很多可以在劍指Offer一書中找到,此文以及所涉及的題目僅供參考,不同的題目有不同的解答方式,切勿死記,一定要基於空間複雜度和時間複雜度再選擇對應的演算法。此文部分題目提供了部分參考程式碼,完整參考程式碼請點選此處檢視。

  • 算術運算題

    1. 質數
      • 大於1的自然數中,只能被1和自身整除的自然數(不要考慮1),質數判斷的參考實現如下:
    bool isPrime(unsigned n) {
        for (int i = 2; i < n; i++) {
            if (n % i == 0) {
                return false;
            }
        }
        return true;
    }
    複製程式碼
    1. 醜數
      • 只能被2、3和5整除的正整數數,1是第一個醜數,醜數判斷的參考實現如下:
      bool isUgly(int n) {
          if (n >= 1) {
              while (n % 2 == 0) {
                  n = n / 2;
              }
              while (n % 3 == 0) {
                  n = n / 3;
              }
              while (n % 5 == 0) {
                  n = n / 5;
              }
              if (n == 1) {
                  return true;
              }
          }
          return false;
      }
      複製程式碼
    2. 斐波那契數列
      • 從0和1開始,之後的數就是前兩個數的和,參考實現如下:
      long long fibonacciSequence(unsigned n) {
      
          int result[] = {0, 1};
          if (n < 2) {
              return result[n];
          }
          long long firstValue = 0;
          long long secondValue = 1;
          long long sum = 0;
          for (int i = 2; i < n; i++) { // n以內的自然數
              sum += firstValue + secondValue;
              firstValue = secondValue;
              secondValue = sum;
          }
          return sum;
      }
      複製程式碼
  • 排序

    1. 氣泡排序(平均複雜度 O(n^2) 且穩定)
      • 每趟確定一個最小值,把最小值往上冒,參考實現如下:
      void bubbleSort(int *a, int len) {
          if (a == nullptr || len < 1) {
              return;
          }
          for (int i = 0; i < len-1; i++) {
              for (int j = 0; j < len-1-i; j++) {
                  if (a[j] > a[j+1]) {
                      swap(a[j], a[j+1]);
                  }
              }
          }
      }
      複製程式碼
    2. 選擇排序(平均複雜度 O(n^2) 且穩定)
      • 選擇一個數字,然後與其餘數字逐個比較,小於該數字則交換,參考實現如下:
      void selectSort(int *a, int len) {
          if (a == nullptr || len < 1) {
              return;
          }
          for (int i = 0; i < len - 1; i++) {
              for (int j = i+1; j < len; j++) {
                  if (a[i] > a[j]) {
                      swap(a[i], a[j]);
                  }
              }
          }
      }
      複製程式碼
    3. 插入排序(平均複雜度 O(n^2) 且穩定)
      • 從頭開始,選擇一個數與前面排好序的數比較,把該數插入到合適的位置,參考實現如下:
      void insertSort(int *a, int len) {
          if (a == nullptr || len < 1) {
              return;
          }
          for (int i = 0; i < len; i++) {
              for (int j = i+1; j > 0; j--) {
                  if (a[j] < a[j-1]) {
                      swap(a[j], a[j-1]);
                  }
              }
          }
      }
      複製程式碼
    4. 快速排序(平均複雜度 O(nlogn) 不穩定)
      • 核心實現是,選擇一個數作為基準值,大於該值的放在右邊,小於該值的放在左邊,最後把該值放在左右分割的位置,遞迴參考實現如下:
      void IVSort::quickSort(int *a, int len) { // step 1
          if (a == nullptr || len < 1) {
              return;
          }
          quickSortImp(a, 0, len-1);
      }
      
      void quickSortImp(int *a, int start, int end) { // step 2
          if (start >= end) {
              return;
          }
          int index = quickSortImpCore(a, start, end);
          if (index > start) {
              end = index-1;
              quickSortImp(a, start, end);
          }
          if (index < end) {
              start = index+1;
              quickSortImp(a, start, end);
          }
      }
      // 快速排序核心實現
      int quickSortImpCore(int *a, int start, int end) { step 3
          int index = start + 1;
          // 預設選第一個作為基本值
          for (int i = index; i <= end; i++) {
              if (a[start] > a[i]) {
                  if (index != i) {
                      swap(a[index], a[i]);
                  }
                  ++index;
              }
          }
          --index;
          swap(a[start], a[index]);
          return index;
      }
      複製程式碼
  • 查詢

    1. 二分法(折半查詢,平均複雜度O(logn))
      • 通過左右索引,不斷縮小查詢區域,適用於已排序數列,參考實現如下:
      bool binarySearch(int *a, int len, int target) {
          if (a != nullptr && len > 0) {
              int start = 0;
              int end = len - 1;
              while (start < end) {
                  int mid = (start + end)/2;
                  if (a[mid] > target) {
                      end = mid-1;
                  }else if (a[mid] < target) {
                      start = mid + 1;
                  }else {
                      return true;
                  }
              }
          }
          return false;
      }
      複製程式碼

鑑於程式碼量大會導致篇幅過長,不方便閱讀,以下題目不在貼出參考程式碼,有需要可點選此處檢視。

  • 字串

    1. 字串翻轉
    2. 字串轉數字
    3. 字串替換
  • 陣列

    1. 找出重複的數
    2. 二維陣列的查詢
    3. 旋轉陣列中最小的數
    4. 陣列中出現超過一半的數字
    5. 陣列中第K大的數
    6. 有序陣列中某個數出現的次數
    7. 調整奇數位位於偶數位前面
  • 連結串列

    1. 刪除指定節點
    2. 刪除重複節點
    3. 倒數第K個節點
    4. 中間節點
    5. 入口節點
    6. 翻轉連結串列
    7. 合併兩個連結串列
    8. 兩個連結串列的首個公共節點
    1. 前序遍歷(遞迴和非遞迴)
    2. 中序遍歷(遞迴和非遞迴)
    3. 後序遍歷(遞迴和非遞迴)
    4. 根據前序和中序構建二叉樹
    5. 根據中序和後序構建二叉樹
    6. 中序遍歷的下一個節點
    7. 翻轉二叉樹
    8. 樹的深度
    9. 根據中序遍歷判斷是否是二叉搜尋樹(BST:Binary Search Tree)
    10. 從上到下列印二叉樹

總結

對於從事程式設計工作的朋友來說,資料結構與演算法是程式設計的基礎(這裡是網路篇iOS篇),也是很多大廠面試的必考題,熟悉演算法不僅僅是為了應付面試,演算法題巧妙的解決思路有助於幫助我們提高程式碼的執行效率和豐富解決問題的思路,實乃防治老年痴呆必備之良藥,望按需取之,另:各位若有更好的解決思路,歡迎一起學習交流。


PS:此文給出的參考程式碼,不一定是最佳的答案,且以上程式碼都是基於Xcode環境編寫,並提供了一些簡單的Test Case,沒有完全覆蓋,大家可以自行編寫case測試,若發現有任何Bug,可通過新浪“Joelixy_”和GitHub及時與我聯絡,我會在第一時間修正,謝謝!

相關文章