一、题目描述
输入一个单向链表,输出该链表中倒数第k个节点。从1开始计数而不是0。比如倒数第一个节点即链表的尾节点。
二、思路分析
首先,题目明确说明输入的是一个单向链表,那么先走到链表的末尾,再回溯(k-1)步这种思路是行不通的。
然后,很自然的可以想到先遍历链表,统计链表节点个数n。那么倒数第k个节点就是从头节点开始的第(n-k+1)个节点。当然,这样的做法是可行的,但是需要遍历链表两次。
一个更加优化的方法是初始化两个指针,均指向链表的头节点。第一个指针从链表的头节点开始遍历走k-1,第二个指针保持不动;从第k步开始,第二个指针也开始从头遍历。这样,两个指针的距离始终保持(k-1)的距离,当第一个指针走到链表尾时,第二个指针恰好指向倒数第k个节点。
三、鲁棒性与编码
鲁棒性也称健壮性,指程序能够判断输入是否合乎规范要求,对不合要求的输入予以相应的处理。
有了上面的解题思路,在正式动手写代码前,我们还应该考虑到常见的几种不合乎要求的输入。比如:
- 输入的链表指针为空
- 输入链表的总数少于k
- 输入的参数k<=0
等3种情况均可能造成程序崩溃。我们在编码前应该充分考虑到并在代码中予以相应的处理。类似情况在博客【编程题目】Pq.6–数值的整数次方(关于代码完整性及错误处理方式的探讨)中也有相关讨论。
这里编码使用到了链表的数据结构(链表节点),读者可参见【算法和数据结构】1.1–数据结构之线性表中链表实现的ListNode类。
class ListNode
{
public:
int element; //Value of this node
ListNode* next; //Pointer to next node
ListNode(const int & elementValue, ListNode* nextNode)
{
element = elementValue;
next = nextNode;
}
};
ListNode* FindKToTail(ListNode* pListHead, unsigned int k)
{
if (pListHead == NULL || k == 0) //输入指针为空及k值不合法情况
return NULL;
ListNode* pAhead = pListHead; //第一个指针
ListNode* pBehind = pListHead; //第二个指针
/*第一个指针先走k-1步*/
for (unsigned int i = 0;i < k - 1;++i)
{
if (pAhead->next != NULL) //对链表总数可能小于k的情况 做处理
pAhead = pAhead->next;
else
return NULL;
}
while (pAhead->next != NULL)
{
pAhead = pAhead->next;
pBehind = pBehind->next;
}
return pBehind;
}