經典面試題:給定單鏈表中**非尾節點**的指針 `node`,在**無法訪問頭指針**情況下刪除該節點。本文揭示「值覆蓋 + 刪除下一節點」的 O(1) 時間 O(1) 空間核心技巧,並分析適用條件與局限。
常規刪除需前驅節點 `pre->next = node->next`,但無 `head` 無法獲取 `pre`。
**將下一節點的值複製到當前節點,然後刪除下一節點。**
node->val = node->next->val; // 1. 覆蓋值
node->next = node->next->next; // 2. 跳過下一節點(邏輯刪除)
// 可選:free(node->next) 釋放內存
效果:邏輯上「當前節點」消失了,實際上是「下一節點」被移除,但數據值保留了下來。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
void deleteNode(struct ListNode* node) {
// 題目保證 node 非空且非尾節點,但防禦性檢查無妨
if (node == NULL || node->next == NULL) return;
struct ListNode *nextNode = node->next;
node->val = nextNode->val; // 複製數據
node->next = nextNode->next; // 旁路下一節點
free(nextNode); // 釋放被旁路節點內存(工程必須)
}
若 `node` 是尾節點,`node->next == NULL`,無法獲取下一節點值與指針,技巧失效。題目明確保證「非尾節點」。
| 場景 | 是否適用 | 說明 |
|---|---|---|
| 單鏈表,給非尾節點指針,無頭指針 | ✅ 完美 | 本題標準解 |
| 雙向鏈表 | ❌ 不需要 | 有 `prev` 指針,常規刪除即可 |
| 需釋放節點關聯資源(如文件句柄、鎖) | ⚠️ 謹慎 | 實際釋放的是下一節點,資源清理邏輯可能錯位 |
| 外部持有該節點指針的其他引用 | ❌ 危險 | 外部引用指向的節點內容被竄改,指針指向下一節點 |
| 循環鏈表 | ✅ 適用 | 邏輯相同,尾節點有下一節點 |
// 構建 1→2→3→4
ListNode *n1=create(1), *n2=create(2), *n3=create(3), *n4=create(4);
n1->next=n2; n2->next=n3; n3->next=n4;
deleteNode(n3); // 刪除節點 3
// 遍歷輸出:1 2 4
// 內存:n3 被 free,n4 存活(值 4 保留在原 n3 位置)
「值覆蓋 + 刪除下一節點」是單鏈表**無頭指針、無前驅指針**條件下刪除非尾節點的**唯一可行 O(1) 方案**。它利用了「鏈表節點對外特徵僅由值序列決定」的本質,以空間換邏輯等價性。面試必考、工程偶用、理解本質最重要。