給定單鏈表頭節點 head 和整數 val,刪除鏈表中所有值等於 val 的節點,返回新頭節點。本文從基礎迭代進階到虛擬頭節點統一邏輯,分析邊界條件處理技巧。
輸入: 1 → 2 → 6 → 3 → 4 → 5 → 6, val = 6 輸出: 1 → 2 → 3 → 4 → 5
public ListNode removeElements(ListNode head, int val) {
// 1. 處理頭節點連續等於 val 的情況
while (head != null && head.val == val) {
head = head.next;
}
if (head == null) return null; // 全部節點被刪除
// 2. 遍歷後續節點
ListNode prev = head;
while (prev.next != null) {
if (prev.next.val == val) {
prev.next = prev.next.next; // 跳過目標節點
} else {
prev = prev.next; // 正常前進
}
}
return head;
}
head == nullpublic ListNode removeElements(ListNode head, int val) {
// 虛擬頭節點,next 指向真實頭節點
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode prev = dummy;
while (prev.next != null) {
if (prev.next.val == val) {
prev.next = prev.next.next; // 刪除
} else {
prev = prev.next; // 前進
}
}
return dummy.next; // 返回真實頭節點
}
head == nullpublic ListNode removeElements(ListNode head, int val) {
if (head == null) return null;
head.next = removeElements(head.next, val); // 遞歸處理後續
return head.val == val ? head.next : head; // 當前節點決策
}
| 優點 | 缺點 | ||
|---|---|---|---|
| 代碼極簡,符合鏈表遞歸定義 | O(n) 棧空間,長鏈表棧溢出風險 | ||
| 無需額外指針變量 | 尾遞歸優化需語言支援 (Java 不支援) |
| 解法 | 時間 | 空間 | 備註 |
|---|---|---|---|
| 直接迭代 | O(n) | O(1) | 邊界處理繁瑣 |
| 虛擬頭節點 | O(n) | O(1) | 推薦,工程實踐標準 |
| 遞歸 | O(n) | O(n) | 教學演示,生產慎用 |
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode fast = dummy, slow = dummy;
// fast 先走 n+1 步
for (int i = 0; i <= n; i++) fast = fast.next;
// 同步前進,fast 到達末尾時,slow 在目標節點前驅
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
掌握虛擬頭節點模式,能解決 80% 以上涉及節點刪除的鏈表題目,是演算法面試與工程實踐的必備技能。