LeetCode:返回倒数第k个结点
1、题目描述
实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。
注意:本题相对原题稍作改动
示例:
输入: 1->2->3->4->5 和 k = 2
输出: 4
说明:
给定的 k 保证是有效的。
2、方法1:两次遍历
-
第一次遍历:计算链表的长度
len
。 -
第二次遍历:从头节点开始,移动
len - k
步,到达倒数第k
个节点。
关键步骤
-
计算链表长度:
-
初始化指针
curr
指向头节点head
,计数器len = 0
。 -
遍历链表,每移动一次
curr
,len
加 1,直到curr
为null
。 -
最终
len
存储的是链表的节点总数。
-
-
定位倒数第 k 个节点:
-
倒数第
k
个节点就是正数第len - k + 1
个节点。 -
由于链表从
head
开始编号为1
,所以只需移动len - k
步即可到达目标节点。 -
例如,链表
1->2->3->4->5
,k=2
(倒数第 2 个节点是4
):-
len = 5
,len - k = 3
。 -
从
head
移动 3 步:1
(初始)→2
→3
→4
,返回4
。
-
-
时间复杂度
-
O(n),其中
n
是链表的长度。-
第一次遍历计算长度:
O(n)
。 -
第二次遍历定位节点:
O(n)
。 -
总时间:
O(2n) = O(n)
。
-
空间复杂度
-
O(1),只使用了常数级别的额外空间(
curr
指针和len
变量)。
public static ListNode kthToLast(ListNode head, int k) {ListNode curr = head;int len = 0;// 第一次遍历:计算链表长度 lenwhile (curr != null) {len++;curr = curr.next;}// 第二次遍历:移动 len - k 步for (int i = 0; i < len - k; i++) {head = head.next;}return head; // 返回倒数第 k 个节点
}
3、方法2:快慢指针
使用快慢指针(Fast-Slow Pointer)来找到链表的倒数第 k
个节点:
-
初始化快慢指针:
fast
和slow
都指向头节点head
。 -
快指针先移动
k-1
步:-
目的是让
fast
和slow
之间相隔k-1
个节点。 -
这样当
fast
到达链表末尾时,slow
正好指向倒数第k
个节点。
-
-
同步移动快慢指针:
-
同时移动
fast
和slow
,每次各移动一步,直到fast
到达最后一个节点(fast.next == null
)。
-
-
返回慢指针:
-
此时
slow
指向的就是倒数第k
个节点。
-
关键点
-
快指针先移动
k-1
步:-
确保
fast
和slow
之间的间隔是k-1
。 -
例如,
k=2
时,fast
先移动 1 步,指向第 2 个节点,slow
仍在第 1 个节点,两者间隔 1(即k-1
)。
-
-
同步移动的条件:
-
while (fast != null && fast.next != null)
:-
fast != null
:避免fast
已经越界(理论上不会发生,因为题目保证k
有效)。 -
fast.next != null
:确保fast
可以移动到下一个节点(即fast
不是最后一个节点)。
-
-
当
fast
是最后一个节点时(fast.next == null
),循环停止,此时slow
指向倒数第k
个节点。
-
时间复杂度
-
O(n),其中
n
是链表的长度。-
快指针最多移动
n
步(先移动k-1
步,再同步移动n-k
步)。 -
慢指针移动
n-k
步。 -
总时间:
O(n)
。
-
空间复杂度
-
O(1),只使用了两个指针(
fast
和slow
),没有额外空间消耗。
public static ListNode kthToLast2(ListNode head, int k) {ListNode fast = head, slow = head;// 快指针先移动 k-1 步for (int i = 0; i < k - 1; i++) {fast = fast.next;}// 同步移动快慢指针,直到快指针到达最后一个节点while (fast != null && fast.next != null) {fast = fast.next;slow = slow.next;}return slow; // 慢指针指向倒数第 k 个节点
}