LeetCode 剑指 Offer 22. 链表中倒数第k个节点
题目描述


题意分析
输入单链表的头节点
head和正整数k,要求返回倒数第k个节点本身。返回的是节点引用,判题会把它当作一条子链表打印出来,所以答案是「该节点及其之后的全部节点」,既不是节点值,也不需要把后面截断。单链表的性质决定了两件事:只能从头往后单向走,不能回退;长度不能 $O(1)$ 拿到。所以「倒数第几个」这种从尾部起算的位置,必须先转换成从头部起算的位置——如果长度是
n,倒数第k个就是从头数第n - k + 1个,即下标n - k。整道题的难点全部集中在「怎么在不知道n的情况下走出n - k步」。题目保证
k合法(不超过链表长度),因此代码不必为k过大兜底;反过来说也不能靠「k越界就返回空」这种兜底逻辑来掩盖偏移量算错的问题。边界情形有三类:
k恰好等于链表长度,答案就是头节点,此时先走的那个指针在同步阶段开始前已经是空;k = 1,答案是尾节点;链表只有一个节点。
解法:快慢指针保持 k 步间距
核心思路
让
fast先走k步,再让fast和slow同速前进。两者始终相距k个节点;当fast到达链表末尾后的null时,slow恰好指向倒数第k个节点。
解题步骤
fast和slow都从头节点出发。fast先前进k步,建立固定间距。- 当
fast != null时,两个指针同时前进一步。fast到达null后返回slow。
代码实现
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast = head;
ListNode slow = head;
for (int i = 0; i < k; i++) {
fast = fast.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
func getKthFromEnd(head *ListNode, k int) *ListNode {
fast, slow := head, head
for i := 0; i < k; i++ {
fast = fast.Next
}
for fast != nil {
fast = fast.Next
slow = slow.Next
}
return slow
}
复杂度分析
- 时间复杂度:$O(n)$,
fast只遍历链表一次。- 空间复杂度:$O(1)$。
关键点总结
fast必须先走k步,而不是k - 1步。- 两个指针同步移动可保持固定间距,无需提前计算链表长度。
- 题目保证
k合法,因此预走阶段不需要额外判空。
易错点总结
- 预走
k - 1步会让答案向后偏一位。- 同步循环写成
fast.next != null会少移动一次,且fast可能已经为空。- 只移动
fast而忘记移动slow,最终会错误地返回头节点。- 题目要求返回节点本身,不能只返回节点值或截断后续链表。
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| 19. 删除链表的倒数第 N 个结点 | 中等 | 要删除而不是返回,指针必须停在目标的前驱,并用哑节点统一处理「删的是头节点」 |
| 141. 环形链表 | 简单 | 固定的是速度差而不是间距,靠两指针能否相遇判环,循环不再以走到 null 为唯一出口 |
| 142. 环形链表 II | 中等 | 相遇之后还要靠一段等式推出入环点,指针关系是解方程解出来的而非人为设定 |
| 876. 链表的中间结点 | 简单 | 用速度比 1:2 代替固定间距,额外要决定偶数长度时取前一个还是后一个中点 |
| LCR 021. 删除链表的倒数第 N 个结点 | 中等 | 与 19 题同题换皮,适合专门练哑节点加双指针的组合写法 |
| 面试题 02.02. 返回倒数第 k 个节点 | 简单 | 指针逻辑与本题完全一致,但要求返回的是节点的值而不是节点引用 |