问题描述
输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。
如果该链表长度小于k,请返回一个长度为 0 的链表。
数据范围:0≤n≤1050≤n≤105,0≤ai≤1090≤ai≤109,0≤k≤1090≤k≤109
要求:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)
进阶:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)
例如输入{1,2,3,4,5},2时,对应的链表结构如下图所示:
其中蓝色部分为该链表的最后2个结点,所以返回倒数第2个结点(也即结点值为4的结点)即可,系统会打印后面所有的节点来比较。
示例1
输入:
{1,2,3,4,5},2
返回值:
{4,5}
说明:
返回倒数第2个节点4,系统会打印后面所有的节点来比较。
示例2
输入:
{2},8
返回值:
{}
解题思路
本题要求找出单链表中倒数第 k 个节点。解决该问题的关键在于:我们需要定位到链表的某个节点,但由于无法直接反向遍历链表,因此只能通过一次正向遍历找到目标节点。可以采用双指针法 解决该问题。其核心思想是利用两个指针 left
和 right
,保持它们之间的距离始终为 k。当 right
遍历到链表的末尾时,left
就正好指向倒数第 k个节点。
代码实现
ListNode* FindKthToTail(ListNode* pHead, int k) {
// write code here
ListNode *left = pHead;
ListNode *right = pHead;
if(pHead == nullptr || k == 0)
{
return nullptr;
}
while (right != nullptr && k)
{
--k;
right = right->next;
}
if(k)
{
return nullptr;
}
while(right)
{
right = right->next;
left = left->next;
}
return left;
}
代码解析
1. 初始化指针与检查边界
left
和 right
都初始化为链表头节点 pHead
。
如果链表为空或 k=0,直接返回 nullptr
,表示无效输入。
ListNode *left = pHead;
ListNode *right = pHead;
if (pHead == nullptr || k == 0)
{
return nullptr;
}
2. 右指针先行 k 步
通过 while
循环,让 right
指针先移动 k步。
如果在移动过程中 k>0 但 right
已经为 nullptr
,说明链表长度小于 k,直接返回 nullptr
。
while (right != nullptr && k)
{
--k;
right = right->next;
}
if (k)
{
return nullptr;
}
3. 双指针同步移动
此时,right
和 left
同步移动,每次 right
向后移动一位,left
也随之移动一位。
当 right
移动到 nullptr
时,left
就指向了倒数第 k 个节点。
while (right)
{
right = right->next;
left = left->next;
}
总结
本题采用双指针法解决,通过让 right
指针先行 k 步,再让 left
和 right
同步移动,最终当 right
到达链表末尾时,left
就指向了倒数第 k个节点。该方法时间复杂度为 O(n),空间复杂度为 O(1),非常高效。需要注意边界情况:如链表为空、k=0 或链表长度小于 k 时,直接返回 nullptr
。这一方法不仅高效简洁,也是链表问题中的经典解法。