「前端進階」面試鏈表再也不怕

圖怪獸_6d4a18670ef300724efa23b645349e9a_43640.png
觀感度:🌟🌟🌟🌟🌟javascript

口味:蒜蓉荷蘭豆前端

烹飪時間:8minjava

本文已收錄在前端食堂同名倉庫 Github github.com/Geekhyt,歡迎光臨食堂,若是以爲酒菜還算可口,賞個 Star 對食堂老闆來講是莫大的鼓勵。

鏈表

數組想必你們都很熟悉,幾乎咱們天天都會操做它。那麼咱們就來對比數組來學習鏈表,首先要明確的是,鏈表和數組的底層存儲結構不一樣,數組要求存儲在一塊連續的內存中,而鏈表是經過指針將一組零散的內存塊串聯起來。可見鏈表對內存的要求下降了,可是隨機訪問的性能就沒有數組好了,須要 O(n) 的時間複雜度。node

下圖中展現了單鏈表及單鏈表的添加和刪除操做,其實鏈表操做的本質就是處理鏈表結點之間的指針。git

在刪除鏈表結點的操做中,咱們只須要將須要刪除結點的前驅結點的 next 指針,指向其後繼便可。這樣,當前被刪除的結點就被丟棄在內存中,等待着它的是被垃圾回收器清除。github

爲了更便於你理解,鏈表能夠類比現實生活中的火車,火車的每節車箱就是鏈表的一個個結點。車箱之間相互鏈接,能夠添加或者移除掉。春運時,客運量比較大,列車通常會加掛車箱。數組

鏈表的結點結構由數據域和指針域組成,在 JavaScript 中,以嵌套的對象形式實現。性能

{
    // 數據域
    val: 1,
    // 指針域
    next: {
        val:2,
        next: ...
    }
}

名詞科普

  • 頭結點:頭結點用來記錄鏈表的基地址,是咱們遍歷鏈表的起點
  • 尾結點:尾結點的指針不是指向下一個結點,而是指向一個空地址 NULL
  • 單鏈表:單鏈表是單向的,它的結點只有一個後繼指針 next 指向後面的結點,尾結點指針指向空地址
  • 循環鏈表:循環鏈表的尾結點指針指向鏈表的頭結點
  • 雙向鏈表:雙向鏈表支持兩個方向,每一個結點不止有一個後繼指針 next 指向後面的結點,還有一個前驅指針 prev 指向前面的結點,雙向鏈表會佔用更多的內存,可是查找前驅節點的時間複雜度是 O(1) ,比單鏈表的插入和刪除操做都更高效
  • 雙向循環鏈表

循環鏈表

雙向鏈表

雙向循環鏈表

LeetCode真題

掌握了鏈表的基礎知識後,咱們拿幾道鏈表的 LeetCode 真題練練手,點擊題目標題便可跳轉到相關題目的描述頁面。學習

1.合併兩個有序鏈表

思路

  • 使用遞歸來解題
  • 將兩個鏈表頭部較小的一個與剩下的元素合併
  • 當兩條鏈表中的一條爲空時終止遞歸

複雜度分析

N+M 是兩條鏈表的長度spa

  • 時間複雜度:O(M+N)
  • 空間複雜度:O(M+N)
const mergeTwoLists = function (l1, l2) {
    if (l1 === null) {
        return l2;
    }
    if (l2 === null) {
        return l1;
    }
    if (l1.val < l2.val) {
        l1.next = mergeTwoLists(l1.next, l2);
        return l1;
    } else {
        l2.next = mergeTwoLists(l1, l2.next);
        return l2;
    }
};

2.環形鏈表

思路

  • 雙指針法
  • 使用快慢不一樣的兩個指針遍歷,快指針一次走兩步,慢指針一次走一步
  • 若是沒有環,快指針會先到達尾部,返回 false
  • 若是有環,則必定會相遇

複雜度分析

  • 時間複雜度:O(N)
  • 空間複雜度:O(1)
const hasCycle = function(head) {
    if (!head || !head.next) {
        return false;
    }
    let fast = head.next;
    let slow = head;
    while (fast !== slow) {
        if (!fast || !fast.next) {
            return false;
        }
        fast = fast.next.next;
        slow = slow.next;
    }
    return true;
};

思路

  • 標記法
  • 遍歷鏈表,經過標記判斷是否有環,若是標記存在則有環。

複雜度分析

  • 時間複雜度:O(N)
  • 空間複雜度:O(1)
const hasCycle = function(head) {
    while (head) {
        if (head.flag) {
            return true;
        } else {
            head.flag = true;
            head = head.next;
        }
    }
    return false;
}

3.反轉鏈表

思路

  • 迭代
  • 初始化前驅節點爲 null,初始化目標節點爲頭節點
  • 遍歷鏈表,記錄 next 節點並反轉指針
  • prev 和 curr 指針分別往前移動一步
  • 反轉結束後,prev 成爲新鏈表的頭節點

複雜度分析

  • 時間複雜度:O(N)
  • 空間複雜度:O(1)
const reverseList = function(head) {
    let prev = null;
    let curr = head;
    while (curr !== null) {
        let next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
};

4.刪除結點的倒數第 N 個節點

思路

  • 刪除倒數第 n 個結點,咱們須要找到倒數第 n+1 個結點,刪除其後繼結點便可
  • 添加 prev 結點,也稱其爲哨兵結點,處理邊界問題
  • 使用雙指針法,快指針先走 n+1 步,而後快慢指針同步往前走,直到 fast.next 爲 null
  • 刪除倒數第 n 個結點,返回 prev.next

複雜度分析

  • 時間複雜度:O(N)
  • 空間複雜度:O(1)
const removeNthFromEnd = function(head, n) {
    let prev = new ListNode(0);
    prev.next = head;
    let fast = prev;
    let slow = prev;
    while (n--) {
        fast = fast.next;
    }
    while (fast && fast.next) {
        fast = fast.next;
        slow = slow.next;
    }
    slow.next = slow.next.next;
    return prev.next;
};

5.求鏈表的中間結點

思路

  • 雙指針法
  • 使用快慢不一樣的兩個指針遍歷,快指針一次走兩步,慢指針一次走一步
  • 當快指針到達終點時,慢指針恰好走到中間

複雜度分析

  • 時間複雜度:O(N) N 是給定鏈表的結點數目
  • 空間複雜度:O(1) 只須要常數空間存放 slow 和 fast 兩個指針
const middleNode = function(head) {
    let fast = head;
    let slow = head;
    while (fast && fast.next) {
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
};

❤️愛心三連擊

1.看到這裏了就點個贊支持下吧,你的是我創做的動力。

2.關注公衆號前端食堂,你的前端食堂,記得按時吃飯

3.本文已收錄在前端食堂Github github.com/Geekhyt,求個小星星,感謝Star。

qrcode.jpg

相關文章
相關標籤/搜索