算法

數(shù)組

優(yōu)點:

  • 構建一個數(shù)組非常簡單
  • 能讓我們在 O(1) 的時間里根據(jù)數(shù)組的下標(index)查詢某個元素

缺點:

  • 構建時必須分配一段連續(xù)的空間
  • 查詢某個元素是否存在時需要遍歷整個數(shù)組,耗費 O(n) 的時間(其中,n 是元素的個數(shù))
  • 刪除和添加某個元素時,同樣需要耗費 O(n) 的時間

力扣:
242. 有效的字母異位詞

鏈表

優(yōu)點:

  • 靈活地分配內(nèi)存空間
  • 能在 O(1) 時間內(nèi)刪除或者添加元素

缺點:

  • 查詢元素需要 O(n) 時間

解題技巧:

  • 利用快慢指針(有時候需要用到三個指針)
  • 構建一個虛假的鏈表頭

力扣:
25. K 個一組翻轉鏈表

算法基本思想:
可以用一個單鏈表來實現(xiàn)
只關心上一次的操作
處理完成上一次的操作后,能在 O(1) 時間內(nèi)查找到更前一次的操作

力扣:
20. 有效的括號
739. 每日溫度

隊列

常用的場景:
廣度優(yōu)先搜索

雙端隊列

基本實現(xiàn):
可以利用一個雙鏈表
隊列的頭尾兩端能在 O(1) 的時間內(nèi)進行數(shù)據(jù)的查看、添加和刪除

常用的場景:
實現(xiàn)一個長度動態(tài)變化的窗口或者連續(xù)區(qū)間

力扣:
239. 滑動窗口最大值

樹的共性:

  • 結構直觀
  • 通過樹問題來考察 遞歸算法 掌握的熟練程度

面試中??嫉臉涞男螤钣校?/p>

  • 普通二叉樹
  • 平衡二叉樹
  • 完全二叉樹
  • 二叉搜索樹
  • 四叉樹
  • 多叉樹
  • 特殊的樹:紅黑樹、自平衡二叉搜索樹

特性:

  • 高度為 h 的滿二叉樹,有 (2^h)-1 個結點
  • 具有 n 個結點的完全二叉樹的高度為 log(n+1) 向上取整,或者 (logn) 向下取整+1

力扣:
230. 二叉搜索樹中第K小的元素

優(yōu)秀的算法往往取決于你采取哪種數(shù)據(jù)結構

優(yōu)先隊列

與普通隊列區(qū)別:
保證每次取出的元素是隊列中優(yōu)先級最高的
優(yōu)先級別可自定義

最常用的場景:
從雜亂無章的數(shù)據(jù)中按照一定的順序(或者優(yōu)先級)篩選數(shù)據(jù)

本質:
二叉堆的結構,堆在英文里叫 Binary Heap
利用一個數(shù)組結構來實現(xiàn)完全二叉樹

特性:
數(shù)組里的第一個元素 array[0] 擁有最高的優(yōu)先級
給定一個下標 i,那么對于元素 array[i] 而言
? 父節(jié)點 對應的元素下標是 (i-1)/2
? 左側子節(jié)點 對應的元素下標是 2*i + 1
? 右側子節(jié)點 對應的元素下標是 2*i + 2
數(shù)組中每個元素的優(yōu)先級都必須要高于它兩側子節(jié)點

其基本操作為以下兩個:
向上篩選(sift up / bubble up)
向下篩選(sift down / bubble down)

另一個最重要的時間復雜度:優(yōu)先隊列的初始化

經(jīng)驗:
求前 k 大,用小根堆,求前 k 小,用大根堆。

力扣:
347. 前 K 個高頻元素

最基本知識點如下:

  • 階、度
  • 樹、森林、環(huán)
  • 有向圖、無向圖、完全有向圖、完全無向圖
  • 連通圖、連通分量
  • 圖的存儲和表達方式:鄰接矩陣、鄰接鏈表

圍繞圖的算法也是各式各樣:

  • 圖的遍歷:深度優(yōu)先、廣度優(yōu)先
  • 環(huán)的檢測:有向圖、無向圖
  • 拓撲排序
  • 最短路徑算法:Dijkstra、Bellman-Ford、Floyd Warshall
  • 連通性相關算法:Kosaraju、Tarjan、求解孤島的數(shù)量、判斷是否為樹
  • 圖的著色、旅行商問題等

必須熟練掌握的知識點:

  • 圖的存儲和表達方式:鄰接矩陣、鄰接鏈表
  • 圖的遍歷:深度優(yōu)先、廣度優(yōu)先
  • 二部圖的檢測(Bipartite)、樹的檢測、環(huán)的檢測:有向圖、無向圖
  • 拓撲排序
  • 聯(lián)合-查找算法(Union-Find)
  • 最短路徑:Dijkstra、Bellman-Ford

力扣:
785. 判斷二分圖

前綴樹

也稱字典樹:
這種數(shù)據(jù)結構被廣泛地運用在字典查找當中

什么是字典查找?
例如:給定一系列構成字典的字符串,要求在字典當中找出所有以“ABC”開頭的字符串

經(jīng)典應用:
搜索框輸入搜索文字,會羅列以搜索詞開頭的相關搜索

重要性質:

  • 每個節(jié)點至少包含兩個基本屬性
    • children:數(shù)組或者集合,羅列出每個分支當中包含的所有字符
    • isEnd:布爾值,表示該節(jié)點是否為某字符串的結尾
  • 根節(jié)點是空的
  • 除了根節(jié)點,其他所有節(jié)點都可能是單詞的結尾,葉子節(jié)點一定都是單詞的結尾

排序

基本的排序算法:

  • 冒泡排序(穩(wěn)定)
  • 插入排序(穩(wěn)定)

??嫉呐判蛩惴?/p>

  • 歸并排序(穩(wěn)定)
  • 快速排序(不穩(wěn)定)
  • 拓撲排序

其他排序算法

  • 希爾排序(不穩(wěn)定)
  • 堆排序
  • 桶排序
  1. 冒泡排序
    算法思想:
    每一輪,從雜亂無章的數(shù)組頭部開始,每兩個元素比較大小并進行交換;直到這一輪當中最大或最小的元素被放置在數(shù)組的尾部;然后,不斷地重復這個過程,直到所有元素都排好位置。

空間復雜度:O(1)
時間復雜度:O(n^2)

function bubbleSort(nums) {
    let hasChange = true;

    for (var i = 0; i < nums.length - 1 && hasChange; i++) {
        hasChange = false;

        for (var j = 0; j < nums.length - 1 - i; j++) {
            if (nums[j] > nums[j + 1]) {
                swap(nums, j, j + 1);
                hasChange = true;
            }
        }
    }
}
  1. 插入排序
    插入排序的算法思想:
    不斷地將尚未排好序的數(shù)插入到已經(jīng)排好序的部分。

空間復雜度:O(1)
時間復雜度:O(n^2)

function insertionSort(nums) {
    for (let i = 1, j; i < nums.length; i++) {
        const tmp = nums[i];
        for (j = i; j > 0 && tmp < nums[j - 1]; j--) {
            nums[j] = nums[j - 1];
        }
        nums[j] = tmp
    }
}

與冒泡排序相比:
在冒泡排序中,經(jīng)過每一輪的排序處理后,數(shù)組后端的數(shù)是排好序的;
在插入排序中,經(jīng)過每一輪的排序處理后,數(shù)組前端的數(shù)都是排好序的。

力扣:
147. 對鏈表進行插入排序

  1. 歸并排序

分治的思想:
歸并排序的核心思想是分治,把一個復雜問題拆分成若干個子問題來求解。

歸并排序的算法思想:
把數(shù)組從中間劃分成兩個子數(shù)組;一直遞歸地把子數(shù)組劃分成更小的子數(shù)組,直到子數(shù)組里面只有一個元素;

時間復雜度:O(nlogn)
空間復雜度:O(n)

function mergeSort(nums, left, right) {
    if (left >= right) {
        return;
    }

    const mid = left + Math.floor((right - left) / 2);
    mergeSort(nums, left, mid);
    mergeSort(nums, mid + 1, right);

    merge(nums, left, mid, right);
}

function merge(nums, left, mid, right){
    let k = i = left, j = mid + 1;
    const numsCopy = nums.slice(left, right + 1);
    while (k <= right) {
        if (i > mid) {
            nums[k++] = numsCopy[j++ - left];
        } else if (j > right) {
            nums[k++] = numsCopy[i++ - left];
        } else if (numsCopy[i - left] < numsCopy[j - left]) {
            nums[k++] = numsCopy[i++ - left];
        } else {
            nums[k++] = numsCopy[j++ - left];
        }
    }
}
  1. 快速排序

快速排序的算法思想:
快速排序也采用了分治的思想;把原始的數(shù)組篩選成較小和較大的兩個子數(shù)組,然后遞歸地排序兩個子數(shù)組;

最優(yōu)時間復雜度:O(nlogn)
最差時間復雜度:O(n^2)
空間復雜度:O(logn)

function quickSort(nums, left, right) {
    if (left >= right) {
        return;
    }

    const p = partition(nums, left, right);

    quickSort(nums, left, p - 1);
    quickSort(nums, p + 1, right);
}
function partition(nums, left, right) {
    // 每次選擇第一個值 left 進行劃分
    // 也可以隨機選擇 left ~ right 中一個
    swap(nums, left, right);

    let i = j = left;
    while(j < right) {
        if (nums[j] < nums[right]) {
            swap(nums, i++, j);
        }
        j++;
    }

    swap(nums, i, right);

    return i;
}

力扣:
215. 數(shù)組中的第K個最大元素

  1. 拓撲排序
    應用場合:
    拓撲排序就是要將圖論里的頂點按照相連的性質進行排序

前提:

  • 必須是有向圖
  • 圖里沒有環(huán)

遞歸和回溯

1. 遞歸

遞歸的基本性質:函數(shù)調用本身

  • 把大規(guī)模的問題不斷地變小,再進行推導的過程

算法思想

  • 要懂得如何將一個問題的規(guī)模變小
  • 再利用從小規(guī)模問題中得出的結果
  • 結合當前的值或者情況,得出最終的結果

通俗理解(自頂向下的算法)

  • 把要實現(xiàn)的遞歸函數(shù),看成已經(jīng)實現(xiàn)好的
  • 直接利用解決一些子問題
  • 思考:如何根據(jù)子問題的解以及當前面對的情況得出答案

力扣:
91. 解碼方法
247. 中心對稱數(shù) II

2. 回溯

回溯:利用遞歸的性質

  • 從問題的起始點出發(fā),不斷嘗試
  • 返回一步甚至多步再做選擇,直到抵達終點的過程

回溯算法是一種試探算法,與暴力搜索最大的區(qū)別:
在回溯算法中,是一步步向前試探,對每一步探測的情況評估,再決定是否繼續(xù),可避免走彎路

回溯算法的精華

  • 出現(xiàn)非法的情況時,可退到之前的情景,可返回一步或多步
  • 再去嘗試別的路徑和辦法
    想要采用回溯算法,就必須保證:每次都有多種嘗試的可能性

解決問題的套路:

function fn(n) {
    // 第一步:判斷輸入或者狀態(tài)是否非法?
    if (input/state is invalid) {
        return;
    }

    // 第二步:判斷遞歸是否應當結束?
    if (match condition) {
        return some value;
    }

    // 遍歷所有可能出現(xiàn)的情況
    for (all possible cases) {
        // 第三步:嘗試下一步的可能性
        solution.push(case)
        // 遞歸
        result = fn(m)
        // 第四步:回溯到上一步
        solution.pop(case)
    }
}

遞歸和回溯可以說是算法面試中最重要的算法考察點之一,很多其他算法都有它們的影子:

  • 二叉樹的定義和遍歷
  • 歸并排序、快速排序
  • 動態(tài)規(guī)劃
  • 二分搜索
    熟練掌握分析遞歸復雜度的方法,必須得有比較扎實的數(shù)學基礎,比如要牢記等差數(shù)列、等比數(shù)列等求和公式。

力扣:
39. 組合總和
52. N皇后 II

深度優(yōu)先搜索

DFS 解決什么問題
DFS 解決的是連通性的問題,即給定一個起始點(或某種起始狀態(tài))和一個終點(或某種最終狀態(tài)),判斷是否有一條路徑能從起點連接到終點。

動態(tài)規(guī)劃

一種數(shù)學優(yōu)化的方法,同時也是編程的方法。

重要屬性:

  • 最優(yōu)子結構 Optimal Substructure
    • 狀態(tài)轉移方程 f(n)
  • 重疊子問題 Overlapping Sub-problems

分類:

  1. 線性規(guī)劃
    1. 求解 dp[i] 形式一:第一種形式,當前所求的值僅僅依賴于有限個先前計算好的值,也就是說,dp[i] 僅僅依賴于有限個 dp[j],其中 j < i。
    2. 求解 dp[i] 形式二:第二種求解 dp[i] 的形式,當前所求的值依賴于所有先前計算好的值,也就是說,dp[i] 是各個 dp[j] 的某種組合,其中 j 由 0 遍歷到 i?1。
  2. 區(qū)間規(guī)劃

0-1 背包問題

非決定性多項式:

  • 時間復雜度
    程序運行的時間隨著問題規(guī)模擴大,增長得有多快。
    • 非多項式級時間復雜度
      指數(shù)級復雜度,如 O(2^n),O(3^n)
      全排列算法,復雜度為 O(n!)
    • 多項式級時間復雜度
      O(1),O(n),O(n * log(n)),O(n2),O(n3) 等。

力扣:
300. 最長上升子序列
70. 爬樓梯
198. 打家劫舍
62. 不同路徑
516. 最長回文子序列

二分搜索算法

高頻真題一

3. 無重復字符的最長子串

4. 尋找兩個有序數(shù)組的中位數(shù)-難

擴展:找前 k 小的數(shù)(堆),第 k 小的數(shù)(快速排序)

23. 合并K個排序鏈表

高頻真題二

56. 合并區(qū)間

拓撲排序:

269. 火星詞典

772. 基本計算器 III

高頻真題三(上)

10. 正則表達式匹配

巧妙解法題收集

416. 分割等和子集 :可以使用背包問題解法,更巧妙的是使用:降序 + 深度搜索 + 剪枝。

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

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

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