數組在上一篇的專欄,中咱們進行了回顧和刷題。javascript
趁熱打鐵,咱們來對比數組來學習鏈表。前端
首先要明確的是,鏈表和數組的底層存儲結構不一樣
,數組要求存儲在一塊連續的內存中,而鏈表是經過指針將一組零散的內存塊串聯起來。java
可見鏈表對內存的要求下降了,可是隨機訪問的性能就沒有數組好了,須要 O(n) 的時間複雜度。
node
下圖中展現了單鏈表及單鏈表的添加和刪除操做,其實鏈表操做的本質就是處理鏈表結點之間的指針。
git
在刪除鏈表結點的操做中,咱們只須要將須要刪除結點的前驅結點的 next 指針,指向其後繼便可。這樣,當前被刪除的結點就被丟棄在內存中,等待着它的是被垃圾回收器清除。github
爲了更便於你理解,鏈表能夠類比現實生活中的火車,火車的每節車箱就是鏈表的一個個結點。車箱之間相互鏈接,能夠添加或者移除掉。春運時,客運量比較大,列車通常會加掛車箱。
面試
鏈表的結點結構由數據域
和指針域
組成,在 JavaScript 中,以嵌套的對象形式實現。數組
{ // 數據域 val: 1, // 指針域 next: { val:2, next: ... } }
年初立了一個 flag,上面這個倉庫在 2021 年寫滿 100 道前端面試高頻題解,目前進度已經完成了 50%。數據結構
若是你也準備刷或者正在刷 LeetCode,不妨加入前端食堂,一塊兒並肩做戰,刷個痛快。性能
瞭解了鏈表的基礎知識後,立刻開啓咱們愉快的刷題之旅,我整理了 6 道高頻的 LeetCode 鏈表題及題解以下。
先明確,刪除倒數第 n 個結點,咱們須要找到倒數第 n+1 個結點,刪除其後繼結點便可。
const removeNthFromEnd = function(head, n) { let prev = new ListNode(0), fast = prev, slow = prev; prev.next = head; while (n--) { fast = fast.next; } while (fast && fast.next) { fast = fast.next; slow = slow.next; } slow.next = slow.next.next; return prev.next; }
n + m 是兩條鏈表的長度
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; } }
先明確想要交換節點共須要有三個指針進行改變。
const swapPairs = (head) => { const dummy = new ListNode(0); dummy.next = head; // 頭部添加哨兵節點 let prev = dummy; while (head && head.next) { const next = head.next; // 保存 head.next head.next = next.next; next.next = head; prev.next = next; // 下面兩個操做將指針更新 prev = head; head = head.next; } return dummy.next; };
若是你對遞歸還以爲掌握的不夠透徹,能夠移步個人這篇專欄
回到本題的遞歸解法:
const swapPairs = function (head) { // 遞歸終止條件 if (head === null || head.next === null) { return head; } // 得到第 2 個節點 let newHead = head.next; // 將第 1 個節點指向第 3 個節點,並從第 3 個節點開始遞歸 head.next = swapPairs(newHead.next); // 將第 2 個節點指向第 1 個節點 newHead.next = head; return newHead; }
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; };
遍歷鏈表,經過 flag 標記判斷是否有環,若是標記存在則有環。(走過的地方插個旗子作標記)
const hasCycle = function(head) { while (head) { if (head.flag) { return true; } else { head.flag = true; head = head.next; } } return false; }
const reverseList = function(head) { let prev = null; let curr = head; while (curr !== null) { // 記錄 next 節點 let next = curr.next; // 反轉指針 curr.next = prev; // 推動指針 prev = curr; curr = next; } // 返回翻轉後的頭節點 return prev; };
const reverseList = function(head) { if (!head || !head.next) return head; // 記錄當前節點的下一個節點 let next = head.next; let reverseHead = reverseList(next); // 操做指針進行反轉 head.next = null; next.next = head; return reverseHead; };
老套路,藉助快慢指針,fast 一次走兩步,slow 一次走一步,當 fast 到達鏈表末尾時,slow 就處於鏈表的中間點了。
const middleNode = function(head) { let fast = head, slow = head; while (fast && fast.next) { slow = slow.next; fast = fast.next.next; } return slow; };