链表的倒数第k个元素
问题描述
给定一个单链表的头节点 head,返回链表中的倒数第 k 个元素。
所谓倒数第 k 个元素,指的是距离链表尾部第 k 个位置的节点。
例如,若链表为 1 -> 2 -> 3 -> 4 -> 5 且 k = 2,则倒数第 2 个元素为 4,即它是距离尾部第 2 个位置的节点。
注意:
k遵循下标从1开始的规则,即k=1代表最后一个元素,k=2代表倒数第二个元素,以此类推。
若 k 大于链表的长度,返回 null。
示例
示例 1:
txt
Input: head = [1,2,3,4,5], k = 2
Output: ListNode with value 4
Explanation: The 2nd to last element is 4
List: 1 -> 2 -> 3 -> 4 -> 5
↑
k=2 from end
示例 2:
txt
Input: head = [1,2], k = 1
Output: ListNode with value 2
Explanation: The 1st to last element (last element) is 2
List: 1 -> 2
↑
k=1 from end
示例 3:
txt
Input: head = [1], k = 1
Output: ListNode with value 1
Explanation: Single element list, k=1 returns the only element
示例4:
txt
Input: head = [1,2,3], k = 5
Output: null
Explanation: k=5 is larger than list length (3), so return null
思路一:两次遍历链表
利用倒数第 k 个元素对应的正数索引刚好是 n-k,我们有:
java
public static ListNode kthToLastNaive(ListNode head, int k) {
ListNode current = head;
int n = 0;
while (current != null) {
n++;
current = current.next;
}
int pos = n - k;
if (pos < 0) {
return null;
}
if (pos == 0) {
return head;
}
if (head == null) {
return null;
}
current = head;
for (int i = 0; i < pos; i++) {
current = current.next;
}
return current;
}
换个思路:把先后变成同步
朴素思路的本质:时间上的先后
在思路一中:
- 动作 A:从 0 0 0 走到 n n n(为了数数)。
- 动作 B:等 A 结束后,再从 0 0 0 走到 n − k n-k n−k(为了定位)。
把"先后"变为"同步"我们可以思考:能不能让这两个动作同时发生?
如果你让两个人在起跑线(head)准备:
- 快跑者 (
Fast):执行动作 A(去探寻终点在哪里)。 - 慢跑者 (
Slow):执行动作 B(去定位目标位置)。
但是有一个问题:如果他们同时起跑,当 Fast 到达终点 n n n 时,Slow 也会到达 n n n。
我们希望 Slow 停在 n − k n-k n−k。
解决办法:
- 让
Slow晚一点出发。 - 让
Fast先走 k k k 步。 - 此时
Fast位于位置 k k k,Slow位于位置 0 0 0。 - 两人再以同样的速度同步前进。
- 当
Fast走完剩余的 n − k n-k n−k 步到达终点时,Slow也刚好走了 n − k n-k n−k 步。 - 此时,
Slow所在的位置正是 ( 0 + n − k ) = n − k (0 + n - k) = n - k (0+n−k)=n−k。
双指针版本
java
public static ListNode kthToLast(ListNode head, int k) {
// Handle edge cases
if (head == null || k <= 0) {
return null;
}
// Initialize two pointers
ListNode fast = head;
// Move fast pointer k steps ahead
for (int i = 0; i < k; i++) {
// k is larger than list length
if (fast == null) {
return null;
}
fast = fast.next;
}
ListNode slow = head;
// Move both pointers until fast reaches the end
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
// slow is now pointing to kth to last element
return slow;
}