目录
一、题目本质
核心模型 :快慢指针速度差------快指针速度是慢指针的 2 倍,快指针到末尾时,慢指针刚好在中间。
这是快慢指针三大应用之一(定差距、速度差、追及判环)。
二、解法
cpp
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode dummy(0, head);
stack<ListNode*> st;
ListNode* p = &dummy;
while (p) {
st.push(p);
p = p->next;
}
// 弹出 n 个,栈顶就是待删结点的前驱
for (int i = 0; i < n; i++) st.pop();
ListNode* prev = st.top();
ListNode* del = prev->next;
prev->next = prev->next->next;
delete del;
return dummy.next;
}
核心不变量 :任意时刻,slow 走过的步数 = fast 走过步数的一半。
证明:
-
初始:slow 走 0 步,fast 走 0 步,0 = 0/2 ✓
-
每轮:slow 走 1 步,fast 走 2 步,比例始终 1:2
当 fast 到末尾(走了 n 步),slow 刚好走了 n/2 步,即中间位置。
按照题目要求,返回第二个中间节点。
cpp
初始:
1 → 2 → 3 → 4 → 5 → 6
↑
slow, fast
第 1 轮:slow=2, fast=3
第 2 轮:slow=3, fast=5
第 3 轮:slow=4, fast=null
slow 停在 4,正是第二个中间结点 ✓
三、复杂度分析
每个结点至多被 fast 访问一次。

四、变形训练
变体1:返回第一个中间结点
只需让 fast 起步就领先一步(即相当于让slow少走一步)
cpp
ListNode* fast = head->next; // 起手就领先
变体2:三分之一位置
快指针速度改为慢指针的 3 倍。
cpp
ListNode* oneThirdNode(ListNode* head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast && fast->next && fast->next->next) {
slow = slow->next;
fast = fast->next->next->next; // 3 倍速
}
return slow; // 停在约 1/3 处
}
变体3:配合断链,用于归并排序找中点
cpp
ListNode* getMidAndCut(ListNode* head) {
if (!head || !head->next) return head;
ListNode* slow = head;
ListNode* fast = head->next; // 找第一个中点
ListNode* prev = nullptr; // 记录 slow 的前驱,用于断链
while (fast && fast->next) {
prev = slow;
slow = slow->next;
fast = fast->next->next;
}
// 断链
if (prev) prev->next = nullptr;
return slow;
}
五、自检
-
能闭眼写出返回第二个中点的版本
-
能改成返回第一个中点(fast=head->next)
-
能解释循环条件是
fast && fast->next -
知道怎么改成 1/3 位置
-
能写出带断链的版本(配合归并排序)
