剑指Offer_15 链表中倒数的第K个节点
2018/5/21 星期一
代码的鲁棒性
鲁棒性对于软件开发的重要性不言而喻,提高代码的鲁棒性的有效途径就是进行防御性编程。这是一种习惯,提高在何处遇见有什么地方可能会出现问题,并为这些问题制定处理方式。比如:当出现找不到文件名时,提醒用户检查输入路径和文件名,当服务器连接不上时,我们可以试图连接备用服务器等。
题目:输入一个链表,输出该链表的倒数第k个结点。为了符合大多数人的阅读习惯,本题从1开始计数,及链表的尾结点为链表的倒数第1个结点。链表结点的定义如下:
class ListNode { int data; ListNode nextNode; }
分析:很自然的一个想法就是先走到链表的尾部,然后往前回溯k个结点,就可以得到第k个元素。但是题目中给出的是单链表结点结构,很明显的是希望我们在单链表中实现这个操作。在单链表中,可以通过一次遍历循环得到链表的长度n,然后再遍历一次,到n-k就可以找到倒数的第k个结点信息。
面试官期待的解法
除了前面的思路,我们可以定义两个指针,前一个指针比后一个指针快k个结点,当后一个指针到达链表末尾的时候,第一个指针就刚好指向了倒数的第k个结点。基于这样的思路可以写出如下的Java代码:
public ListNode FindKthToTail(ListNode pListHead, int k) {
ListNode pAhead = pListHead;
ListNode pBehind = null;
// pAhead先走k步
for (int i = 0; i < k; i++) {
pAhead = pAhead.nextNode;
}
// 一起顺序顺序查找
pBehind = pListHead;
while (pAhead.nextNode != null) {
pAhead = pAhead.nextNode;
pBehind = pBehind.nextNode;
}
return pBehind;
}
当我们洋洋洒洒的写下上面代码的时候,很难让人满意。有如下的情况让代码崩溃:
- 输入的pListHead为空
- 输入的pListHead为头结点的链表节点总数小于k
修复后代码如下:
public ListNode FindKthToTail(ListNode pListHead, int k) {
if (pListHead == null || k == 0) {
return null;
}
ListNode pAhead = pListHead;
ListNode pBehind = null;
// pAhead先走k步
for (int i = 0; i < k; i++) {
if (pAhead.nextNode != null) {
pAhead = pAhead.nextNode;
} else {
return null;
}
}
// 一起顺序顺序查找
pBehind = pListHead;
while (pAhead.nextNode != null) {
pAhead = pAhead.nextNode;
pBehind = pBehind.nextNode;
}
return pBehind;
}
测试用例:
- 功能测试:第k个结点是链表的中间结点,是头结点;是尾结点
- 特殊输入测试:链表头结点为null,链表的结点小于k,k等于0。
相关题目
求一个链表的中间结点?如果链表的结点总数为奇数,则返回中间结点,如果链表的结点为偶数,则放回中间结点的任意一个结点。
解决思路:我们可以定义两个指针,一个指针一次走一步,一个每次走两步。当走的快的指针走到末尾的时候,走的慢的指针刚好在链表的中间。
判断一个单项链表是否形成了环形结构?和前面一样,如果走的快的指针追上了走的慢的指针,那么链表就是环形链表;如果走的快的指针走到了链表的末尾都没有追上第一个指针,那么链表就不是环形链表。
举一反三
当用一个指针来遍历链表不能解决问题的时候,可以尝试用两个指针来遍历链表。可以让其中的一个链表遍历的快些(比如一次走两步)或者先走若干步。