Linked List
介紹
Linked List 是節點與節點透過指標相互連接所形成的一條鏈結串列,和陣列一樣是一種線形(linear)資料結構,而與陣列不同之處在於 Linked List 儲存資料的記憶體位置為「非連續」。
節點(node)由兩個部分組成:
- value:值
- next:指向下一個節點位址的指標
優點:插入或刪除節點時比較簡單,僅需調整 next 所指向的位址
缺點:存取特定節點較費時,因為不像陣列有索引(index),所以會需要從頭開始找,時間複雜度為 BigO(n)
種類
(待補圖)
- Singly linked list 最基礎的 linked list,鏈結方向為單向
- Double linked list 鏈結方向為雙向,也就是每個節點除了有指向下一個節點的指標(next)外,還有指向上一個節點的指摽(prev)
- Circular linked list 串列的頭和尾彼此連接在一起,行程環狀鏈結串列
實作
Node
class ListNode {
constructor(value) {
this.value = value;
this.next = null;
}
}
Linked List
class LinkedList {
constructor() {
this.head = null;
this.length = 0;
}
Methods...
}
Methods:新增(append)
在 Linked List 尾端新增節點。分別處理兩種情況:
- Linked List 內尚無節點:將 head 指向新節點
- Linked List 已存在節點:遍歷至串列尾端再將 next 指向新節點
append(value) {
const newNode = new ListNode(value);
if (!this.head) {
this.head = newNode;
} else {
let cur = this.head;
while (cur.next !== null) {
cur = cur.next;
}
cur.next = newNode;
}
this.length++;
}
Methods:刪除(removeAt)
移除指定 index 節點。
- 指定 index 為第一個節點:將串列的頭指向下一個節點
- 反之,遍歷找到指定 index 節點後,將前一個節點的 next 指向下一個節點
removeAt(index) {
檢查 index 是否為有效值
if (index < 0 || index >= this.length) return null;
let current = this.head;
let prev = null;
let idx = 0;
if (index === 0) {
this.head = current.next;
} else {
while (idx < index) {
prev = current;
current = current.next;
idx++;
}
prev.next = current.next;
}
this.length--;
}
Methods:插入(insertAt)
insertAt(index, val) 接受兩個參數:
- index:指定欲插入串列的位置
- val:新節點的值
insertAt(index, val) {
if (index < 0 || index > this.length) return null;
let newNode = new ListNode(val);
let current = this.head;
let prev = null;
let idx = 0;
if (index === 0) {
this.head = newNode;
node.next = current;
} else {
while (idx < index) {
prev = current;
current = current.next;
idx++;
}
prev.next = newNode;
newNode.next = current;
}
this.length++;
}
練習
876. Middle of the Linked List
給定一個單向 Linked List,請找出其中間的節點並回傳。
方法一、計算 Linked List 總長度
function findMiddleNode(head) {
let listLength = 1;
let curr = head;
while (curr.next !== null) {
listLength++;
curr = curr.next;
}
const middleIndex = Math.floor(listLength / 2);
let middleNode = head;
for (let i = 0; i < middleIndex; i++) {
middleNode = middleNode.next;
}
return middleNode;
}
方法二、快慢指標
function findMiddleNode(head) {
let slow = head;
let fast = head;
while (fast !== null && fast.next !== null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
141. Linked List Cycle
給定一個單向鏈結串列的 head,請判斷這個鏈結串列是否包含一個「環」。
function hasCycle(head) {
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
// 然後再檢查是否相遇
if (slow === fast) return true;
}
return false;
}
142. Linked List Cycle II
給定一個帶有「環」的鏈結串列,請找出環開始的節點。如果沒有環,則回傳 null。
function detectCycle(head) {
let slow = head;
let fast = head;
while (fast && fast.next !== null) {
slow = slow.next;
fast = fast.next.next;
//當 slow === fast,代表已經進入了環中。
if (slow === fast) {
//這時再從 head 新起一個指針 finder,和 slow 一起一步一步走,它們會在環的起點相遇。
let pointer = head;
while (pointer !== slow) {
pointer = pointer.next;
slow = slow.next;
}
return finder;
}
}
return null;
}