题目描述
为了实现只遍历链表⼀次就能找到倒数第k个结点, 我们可以定义两个指针,初始化均指向头结点。
第⼀个指针 p1从链表的头指针开始遍历向前⾛ k-1 步, 第⼆个指针 p2保持不动;
从第k步开始, 第⼆个指针也开始从链表的头指针开始遍历,即双指针共同移动;
由于两个指针的距离保持在 k-1, 当第⼀个(⾛在前⾯的) 指针到达链表的尾结点时, 第⼆个指针(⾛在后⾯的) 指针与尾结点的距离为 k-1,正好是倒数第k个结点。
考虑到代码的鲁棒性,还需要对一些特殊情况进行处理:
- 输入链表为空
- 输入链表的结点个数少于 k个,由于在 for循环中第一个指针会在链表上向前⾛ k- 1步, 这时候可能会由于超出了链表的长度,导致空指针造成程序崩溃。
- 输入参数 k为 0,由于 k是⼀个⽆符号整数, 那么在 for循环中 k- 1得到的将不是 -1,而是 4294967295(⽆符号的 0xFFFFFFFF) 。因此 for循环执⾏的次数远远超出我们的预计, 同样也会造成程序崩溃。
Java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
if (head == null || k == 0) // 链表为空或者输入参数 k为0
return null;
ListNode p1 = head, p2 = head; // 定义快、慢指针
// 快指针先走 k-1步
for (int i=0; i<k-1; i++){
if (p1 == null && i<k) // 链表结点个数少于 k个,快指针在向前走 k-1步的过程中就可能跳出了链表的范围,变成空指针
return null;
p1 = p1.next;
}
// 走完 k步后,此时两个指针距离为 k-1,
// 接下来共同移动,直到快指针到达链表尾结点时,此时慢指针与尾结点距离为 k-1,正好为倒数第 k个结点
while (p1.next != null){
p1 = p1.next;
p2 = p2.next;
}
return p2;
}
}
简化代码,通过一个辅助变量 t 代替第一个 for 循环
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
if (head == null || k == 0) return null;
ListNode p1 = head, p2 = head;
int t = 0;
while (p1 != null){
if (t>k-1) // p1先走 k-1步之后,p2才开始移动
p2 = p2.next;
p1 = p1.next;
t++;
}
if (p1 == null && t<k) return null; // 链表长度小于 k
return p2;
}
}
时间复杂度:O(n),快指针遍历了整个链表,走了 n步,慢指针走了 n-k 步。
空间复杂度:O(1),双指针只需要常数大小的额外空间。
参考
https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/solution/mian-shi-ti-22-lian-biao-zhong-dao-shu-di-kge-j-11/