本文是笔者根据自己刷题的过程得出的方法分享,如有错漏,欢迎指正,提供了三种解决反转链表问题的方法,递归,栈,双指针。
具体题目如下:
TIPS:本文提供的方法思路均未严格遵循题目的时间和空间复杂度要求
这题我第一反应是使用递归去反转,因此我先介绍一下我自己的解题思路和代码。递归的思想是将一个大问题分成一个或多个与原问题相似但规模更小的子问题,然后通过解决子问题来解决大问题。我们由此来分析这个问题可以将其化成反转两个节点指向的问题 ,假设有节点m和n,且m的next指向n,则反转他们可以为m->next->next=m;m->next=NULL;这里把n的next指向了m,然后断开m和n的指向,完成反转。因此我们可以将其扩大到整个问题。
递归需要拥有基线条件和递归条件,这里基线条件也就是终止条件,防止程序无限递归,递归条件用来使问题规模减小以达到基线条件。本题我们可以将遍历到链表尾做为终止条件。结合上面的子问题反转有了这样的代码:
struct ListNode* ReverseList(struct ListNode* head )
{
//递归终止条件
if(head == NULL || head->next == NULL)
{
return head;
}
//递归
struct ListNode* rev;
rev = ReverseList(head->next);
//反转
head->next->next = head;
head->next = NULL;
return rev;
}
当我们遍历到链表尾部时,触发终止条件开始回弹,返回值是head也就是链表尾结点的值(递归的参数是head->next),接着不断反转直到链表被完全反转,完成题目要求。
这个题目还有其他的解法,提到反转我们应该可以想到使用栈这种先进后出的结构,将旧链表逐个入栈然后取出放入新链表完成反转。我们来具体分析一下这种解法。
假设我们有如下链表:
首先我们将其从头结点开始全部入栈
取栈顶再依次出栈,得到反转后的链表
过程还是很好理解的,涉及的代码如下,不在过多解释,详细请看注释。
// 反转链表的函数
struct ListNode* reverseList(struct ListNode* head) {
if (head == NULL) return NULL;
// 创建一个栈
struct ListNode* stack[1000]; // 假设链表长度不超过1000
int top = -1;
// 将所有节点压入栈中
struct ListNode* current = head;
while (current != NULL) {
stack[++top] = current;
current = current->next;
}
// 从栈中弹出节点,重新连接链表
head = stack[top];
current = head;
while (top > 0) {
current->next = stack[--top];
current = current->next;
}
current->next = NULL; // 最后一个节点的next指针设为NULL
return head;
}
我们还可以使用双指针来反转链表,通过改变指针的指向来解决问题,具体的流程如下:
假设链表为 1 -> 2 -> 3 -> 4 -> 5 -> NULL
,反转过程如下:
-
初始状态:
pHead
指向1
p
指向1
tmp
指向2
-
断开第一个节点的链接:
1 -> NULL
-
第一次循环:
tmp
指向3
2 -> 1
pHead
指向2
p
指向3
-
第二次循环:
tmp
指向4
3 -> 2
pHead
指向3
p
指向4
-
第三次循环:
tmp
指向5
4 -> 3
pHead
指向4
p
指向5
-
第四次循环:
tmp
指向NULL
5 -> 4
pHead
指向5
p
指向NULL
-
循环结束,返回
pHead
,即反转后的链表头节点5
。
最终反转后的链表为 5 -> 4 -> 3 -> 2 -> 1 -> NULL
。
// 反转链表的函数
struct ListNode* ReverseList(struct ListNode* pHead ) {
// 处理特殊情况:链表为空
if(pHead == NULL) {
return NULL;
}
// 处理特殊情况:链表只有一个节点
else if(pHead->next == NULL) {
return pHead;
}
// 初始化指针
struct ListNode* p = pHead; // p 指向当前节点
struct ListNode* tmp = pHead->next; // tmp 指向当前节点的下一个节点
// 断开第一个节点的链接,使其成为反转后链表的尾节点
pHead->next = NULL;
// 使用双指针法反转链表
while(tmp != NULL) {
tmp = p->next; // tmp 指向下一个节点
p->next = pHead; // 反转当前节点的 next 指针
pHead = p; // 更新头节点
p = tmp; // 移动到下一个节点
}
// 返回反转后的链表头节点
return pHead;
}
此外我们可以使用头插法来构建新的链表以达成反转,这里不在赘述,读者可以自己思考。