面试题16:反转链表
一.题目描述
定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。
链表结点定义如下:
struct ListNode
{
int m_nKey;
ListNode* m_pNext;
};
二.分析问题
1.举例分析
2.解题思路
解法1
为了正确地反转一个链表,需要调整链表中指针的方向。为了将调整指针这个复杂的过程分析清楚,我们可以借助图形来直观地分析。如图所示的链表中,h、i和j是3个相邻的结点。假设经过若干操作,我们已经把结点h之前的指针调整完毕,这些结点的m_pNext都指向前面一个结点。接下来我们把i的m_pNext指向h,此时的链表结构如图所示。
注:
(a)一个链表。
(b)把i之前所有的结点的m_pNext都指向前一个结点,导致链表在结点i、j之间断裂。
不难注意到,由于结点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的结点。
需要注意的问题
- 输入的链表头指针为NULL或者整个链表只有一个结点时,程序立即崩溃。
- 反转后的链表出现断裂。
- 返回的反转之后的头结点不是原始链表的尾结点。
解法2
解法2和解法1的唯一差别
- 解法1反转后链表的头结点是原始链表的尾结点
- 解法2反转后链表的头结点并没有改变
三.代码
解法1【就地逆置】
ListNode* ReverseList(ListNode* pHead)
{
ListNode* pReverseHead = NULL;
ListNode* pNode = pHead;
ListNode* pPrev = NULL;
while (pNode != NULL)
{
ListNode* pNext = pNode->m_pNext;
if (pNext == NULL)
{
pReverseHead = pNode;
}
pNode->m_pNext = pPrev;
pPrev = pNode;
pNode = pNext;
}
return pReverseHead;
}
或
void ReverseList(ListNode* pHead)
{
if (pHead == NULL) reuturn NULL;
ListNode* p = pHead->m_pNext;
if (p == NULL) return NULL;
ListNode* q = NULL;
ListNode* s = NULL;
while (p != NULL)
{
q = p->m_pNext;
p->m_pNext = s;
s = p;
p = q;
}
pHead->m_pNext = s;
}
解法2【头插法】
void ReverseList(ListNode* pHead)
{
if (pHead == NULL) reuturn NULL;
ListNode* q = NULL;
ListNode* p = pHead->m_pNext;
pHead->m_pNext = NULL;
if (p == NULL) return NULL;
while (p != NULL)
{
q = p;
p = p->m_pNext;
q->m_pNext = pHead->m_pNext;/*第一次执行这条语句时,q->next=NULL,此时q变为尾节点*/
pHead->m_pNext = q;
}
}
解法3【递归】
Node* reverse(Node* list) {
if (list == nullptr || list->next == nullptr) {
return list;
}
Node* last = reverse(list->next);
list->next->next = list;
list->next = nullptr;
return last;
}
示例:
现有如下一单链表,使用递归的方法进行逆置。
当执行到最后一个
Node* last = reverse(list->next);
后,last和list的位置如下图
接着执行
list->next->next = list;
list->next = nullptr;
此时 return last,返回到上一个递归调用点,此时last和list的位置如下
接着再执行
list->next->next = list;
list->next = nullptr;
此时return last,last就是逆置后链表的新头结点。