题目
定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。链表的定义如下:
struct ListNode
{
int m_nkey;
ListNode* m_pNext;
};
分析
不难注意到,由于结点 i 的m_pNext指向了它的前一个结点,导致我们无法在链表中遍历到结点 j 。为了避免链表在结点i处断开,我们需要在调整结点 i 的m_pNext之前,把结点 j 保存下来。
也就是说,我们在调整结点 i 的 m_pNext指针时,除了需要知道结点 i 本身之外,还需要 i 的前一个结点 h,因为我们需要把结点 i 的m_pNext 指向结点 h。同时,我们还事先需要保存 i 的 一个结点 j,以防止链表断开。因此相应地我们需要定义 3个指针,分别指向当前遍历到的结点、它的前一个结点 及后一个结点。
最后我们试着找到反转后链表的头结点。不难分析出反转后链表的头结点是原始链表的尾结点。什么结点是尾结点?自然是 m_pNext 为 NULL 的结点。
ListNode *ReverseList( ListNode* pHead )
{
ListNode* pReversedHead = NULL;
listNode* pNode = pHead;
ListNode* pPrev = NULL;
while( pNode != NULL )
{
ListNode* pNext = pNode->m_pNext;
if( pNext == NULL )
pReversedHead = pNode;
pNode->m_pNext = pPrev;
pPrev = pNode;
pNode = pNext;
}
return pReversedHead;
}
在上述代码中发现应聘者的代码经常出现如下 3 种问题:
(1)输入的链表头指针为NULL或者整个链表只有一个结点时,程序立即崩溃。
(2)反转后的链表出现断裂。
(3)返回的反转之后的头结点不是原始链表的尾结点。
为了避免代码出现问题,在写代码的时候需要提前想好测试用例。在写出代码之后,立即用事先准备好的测试用例检查测试。如果面试是以手写代码的方式,也要在心里默默运行代码做单元测试。只有确保代码通过测试之后,再提交面试官。值得注意的是:自己多花时间找出问题并修正问题,比在面试官找出问题之后再取慌慌张张修改代码要好得多。其实面试官检查应聘者代码的方法也是用事先准备好的测试用例来测试。如果 应聘者能够想到这些测试用例,并用它们来检查测试自己的代码,那就能保证有备无患、万无一失了。
想到的测试用例
(1)输入的链表头指针是NULL
(2)输入的链表只有一个结点
(3)输入的链表有多个结点。
测试用例&代码
(1)功能测试(输入的链表含有多个结点,链表中只有一个结点 )
(2)特殊输入测试(链表头结点为NULL指针)
本题考点
(1)考察应聘者对链表、指针的编程能力
(2)特别注重考察应聘者思维的全面性及写出来的代码的鲁棒性。
本题扩展
用递归实现同样的反转链表的功能