单链表无法回退,因无随机访问能力;先求长度再遍历虽可行但需两次遍历且易错边界;双指针法(快指针先走k步后同步移动)一次遍历即得倒数第k节点,并天然支持越界防护与工程健壮性。

为什么不能先遍历一遍再回退?
因为链表没有随机访问能力,next 指针只能单向走,根本没法“回退”到倒数第 K 个位置。有人想先算长度 L,再走 L-K 步——可行但多了一次遍历,不优雅,也容易在边界上出错(比如 K > L)。
真正实用的做法是双指针:快指针先走 K 步,然后快慢指针一起走,快指针到尾时,慢指针刚好停在倒数第 K 个节点。
注意几个关键点:
- 必须检查
K 或头指针为空,直接返回 <code>nullptr - 快指针走
K步后若变成nullptr,说明K超出链表长度,按题意通常返回nullptr - 用
while (fast != nullptr && --K > 0)这类写法容易漏判K刚好等于长度的情况,推荐分两步:先单独走K步并校验,再同步移动
双指针实现要注意的三处细节
标准写法看似简单,但实际调试时经常卡在空指针或越界上。核心不是“怎么写”,而是“怎么保安全”。
推荐这样组织逻辑:
- 第一步:用
for (int i = 0; i 让 <code>fast走K步;每步前检查fast == nullptr,一旦为真立刻返回nullptr - 第二步:当
fast非空时,再用while (fast->next != nullptr)同步推进;这里用fast->next判断而非fast,是为了让slow停在倒数第K个,而不是它的前一个 - 第三步:如果题目要求返回节点值而非指针(如 LeetCode 19),记得确认返回前
slow不为空——虽然前面已校验,但代码合并时容易疏忽
示例片段:
ListNode* getKthFromEnd(ListNode* head, int k) {
if (!head || k next;
}
while (fast) {
fast = fast->next;
slow = slow->next;
}
return slow;
}
递归解法为什么很少用?
递归确实能自然表达“到底再往回数”的语义,靠后序遍历+全局计数器或引用传参也能实现。但它有硬伤:
- 空间复杂度是
O(N),而双指针是O(1);链表很长时,栈溢出风险真实存在 - 无法提前终止:哪怕
K=1,也要先递归到末尾再开始计数,效率白丢 - C++ 没有尾递归优化保证,编译器不一定帮你压平调用栈
- 面试中如果写递归,面试官大概率会追问“能不能改成迭代”,等于多一道坎
除非题目明确要求用递归(比如考察对后序遍历的理解),否则别主动选这条路。
LeetCode 19 题删倒数第K个节点,和找节点有啥区别?
找节点只要返回指针;删节点必须拿到**前一个节点**才能改 next 指针。所以不能只让 slow 停在目标节点,得让它停在目标的前驱。
常见错误是把快指针多走一步(K+1 步),但这样会引入新问题:
- 当要删的是头节点(即
K == length),slow会停在nullptr,没法操作 - 正确做法是加虚拟头节点
dummy,让slow从dummy出发,快指针仍走K步,之后同步移动 —— 这样slow最终一定指向待删节点的前一个,且dummy->next可安全返回 - 别忘了最后
delete节点,C++ 手动内存管理这点不能偷懒
真正容易被忽略的,是边界判断顺序:必须先验证 fast 是否有效,再推进 slow,否则 slow->next = slow->next->next 可能解引空指针。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











