反转链表详解(C语言)

反转链表详解(C语言)

206. 反转链表 - 力扣(LeetCode)

一、题目描述

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

请添加图片描述

二、解题方法

1.头插法——创建一个新的链表

思路

直接创建一个新的头节点构成一个新链表,然后依次将原链表的每个节点按头插法插入新链表当中,利用头插法插入后节点顺序与原始输入顺序相反的特点,就达到了反转链表的目的。

具体操作
  • 创建一个新的头节点newHead,定义两个指针变量:cur用来遍历原链表各个节点、temp用来记录cur的下一个节点,防止找不到原链表以及返回反转后的新链表。
  • 依次将原链表各个节点头插到新链表,直到cur遍历到原链表结尾NULL
  • 最后用temp记录新链表的第一个节点,即newHead->next,释放newHead,返回temp
AC代码(附注释)
struct ListNode* reverseList(struct ListNode* head) {
    // 给这个结构体起一个别名,方便后续使用
    typedef struct ListNode Node;
    //创建新链表
    Node* newHead = (Node*)malloc(sizeof(Node));
    newHead->next = NULL;
    //创建两个指针变量
    Node* cur = head;//cur用来遍历原链表
    Node* temp;//temp用来暂存节点
    //遍历原链表,依次将节点头插至新链表
    while (cur != NULL) {
        //先将当前节点cur的下一个节点暂存到temp中,因为接下来要改变cur->next的指向
        temp = cur->next;
        //头插法
        cur->next = newHead->next;
        newHead->next = cur;
        //将cur指针向后移动一位,指向下一个要处理的原链表节点
        cur = temp;
    }
    //反转完成,将temp指向新链表真正的头节点
    temp = newHead->next;
    //释放内存
    free(newHead);
    //返回反转后的链表
    return temp;
}

**实际上,再定义一个新的链表,实现链表元素的反转,是对内存空间的浪费,其实只需要改变原链表next指针的指向,直接将链表反转。**以下是几种实现方法:

2.迭代法——双指针实现

思路

用两个指针直接将原链表每个节点的next的指向反转。

具体操作
  • 定义指针cur初始化为head,指针prev初始化为NULL,再定义一个指针temp用来暂存节点。
  • 先用temp保存cur的下一个节点,然后让cur->next的指向prev,这样就反转一个节点了。
  • 不断移动curprev执行上面的操作,直到cur指向原链表的末尾NULL
  • 最后prev就指向了新的头节点,返回prev即可。
AC代码(附注释)
struct ListNode* reverseList(struct ListNode* head) {
    //定义一个指针cur,初始时让它指向链表的头节点head,用于遍历链表
    struct ListNode* cur = head;
    //定义一个指针prev,初始化为NULL,它将用于记录当前节点cur的前一个节点
    struct ListNode* prev = NULL;
    //定义一个临时指针temp,用于暂存节点
    struct ListNode* temp;
    //遍历链表,反转每个节点的next指向
    while (cur != NULL) {
        //先将当前节点cur的下一个节点暂存到temp中,因为接下来要改变cur->next的指向
        temp = cur->next;
        //将当前节点cur的下一个节点指向它的前一个节点prev,实现反转
        cur->next = prev;
        //将prev指针更新为当前节点cur,因为此时cur已经完成了反转操作,成为了新的“前一个节点”
        prev = cur;
        //将cur指针向后移动一位,指向下一个要处理的链表节点
        cur = temp;
    }
    //当遍历完整个链表后,prev指针将指向原来链表的最后一个节点,而这个节点在反转后成为了新的链表头节点
    //所以返回prev,它就是反转后链表的头节点
    return prev;
}

3.递归法——从前往后反转

思路

我们可以通过迭代法得到递归法。

具体操作
  • 创建一个递归函数reverse,用于反转链表,参数为prevcur,与迭代法逻辑相同。
  • 初始参数为NULLhead,相当于初始化prevcur操作,当curNULL时,递归结束,返回prev
  • 每次递归先用temp暂存cur->next,然后将cur->next的指向反转,下一次递归传入参数为curtemp,相当于迭代法中移动prevcur的操作
AC代码(附注释)
struct ListNode* reverse(struct ListNode* prev, struct ListNode* cur) {
    // 如果当前要处理的节点cur为NULL,说明已经到达链表末尾,此时prev就是反转后的链表头节点
    //直接返回prev即可
    if (cur == NULL) {
        return prev;
    }
    //先将当前节点cur的下一个节点暂存到temp中,因为接下来要改变cur->next的指向
    struct ListNode* temp = cur->next;
    //反转cur->next的指向
    cur->next = prev;
    //相当于迭代法中的:
    // prev = cur;
    //cur = temp;
    return reverse(cur, temp);
}
struct ListNode* reverseList(struct ListNode* head) {
    //相当于迭代法中的:
    //struct ListNode* prev = NULL;
    //struct ListNode* cur = head;
    return reverse(NULL, head);
}

这个递归写法和迭代法实质都是从前往后翻转指针指向,其实还有一种与迭代法思路不同的递归写法:从后往前翻转指针指向。

4.递归法Ⅱ——从后往前反转

思路

先处理链表为空或仅有一个节点的特殊情况,然后通过递归地反转子链表,并逐步调整节点间的连接关系,最终完成整个链表的反转,并返回反转后的链表头节点。

具体操作
  • 如果链表为空或者只有一个节点,直接返回,无需反转。
  • 递归调用reverseList函数,传入head->next,将返回值保存在newHead中,这样就可以先反转以head->next为头节点的子链表,得到反转后的子链表的头节点newHead
  • 在递归回溯过程中,当到达链表的最后一个节点时,会开始从链表的尾部开始反转操作。
  • 将当前节点 head 的下一个节点的下一个节点指向当前节点 head,即 head->next->next = head,实现指针的反转操作 ,将当前节点head的下一个节点设置为NULL,因为完成一次反转后,原来的head节点在反转后的链表中就变成了尾节点。
  • 最后返回newHead,即反转后链表的头节点。
AC代码(附注释)
struct ListNode* reverseList(struct ListNode* head) {
    // 如果链表为空(即head指针为NULL),或者链表只有一个节点(即head的下一个节点NULL)
    // 在这两种情况下,链表无需反转,直接返回原链表的头节点head即可
    if (head == NULL || head->next == NULL) {
        return head;
    }
    //递归调用reverseList函数,传入head的下一个节点head->next
    //先反转以head->next为头节点的子链表部分
    //递归调用后,会得到反转后的子链表的头节点
    struct ListNode* newHead = reverseList(head->next);
    //完成反转
    head->next->next = head;
    //尾节点
    head->next = NULL;
    //返回反转后的链表的头节点
    return newHead;
}

5.原地反转

思路

通过迭代的方式,利用两个指针 beginend,在循环过程中逐步调整链表节点之间的连接关系,将节点依次从原链表的顺序反转过来,最终得到反转后的链表头节点并返回。

具体操作
  • 先判断传入的链表头节点head是否为空。如果head为空,这意味着链表本身就是空链表,无需进行反转操作,直接返回head即可。
  • 定义两个指针beginendbegin指针初始化为链表的头节点headend指针初始化为头节点head的下一个节点,它是接下来要移动和操作的节点,通过对end节点的处理来逐步实现链表的反转。
  • 先将begin节点的下一个节点指向end节点的下一个节点,防止后面链表丢失;接着将end节点的下一个节点指向原来的头节点head,实现了将end节点插入到链表头部的操作;然后将head指针更新为end指针所指向的节点;最后将end指针更新为begin节点的下一个节点;
  • 循环结束后,意味着已经遍历完整个链表并完成了所有节点的反转操作。此时head指针所指向的节点就是反转后的链表头节点,直接返回head即可。
AC代码(附注释)
struct ListNode* reverseList(struct ListNode* head) {
    //如果链表的头节点为空(即链表为空链表),那么直接返回这个空的头节点
    if (head == NULL) {
        return head;
    }
    //定义一个指针begin,初始时让它指向链表的头节点head,它将作为反转过程中当前处理的起始节点
    struct ListNode* begin = head;
    //定义一个指针end,初始时让它指向头节点head的下一个节点,它将作为反转过程中要移动和处理的节点
    struct ListNode* end = head->next;
    while (end != NULL) {
        //先将begin节点的下一个节点指向`end`节点的下一个节点,防止后面链表丢失
        begin->next = end->next;
        //将end节点的下一个节点指向原来的头节点head,实现了将end节点插入到链表头部的操作
        end->next = head;
        // 将head指针更新为end指针所指向的节点
        head = end;
        //将end指针更新为begin节点的下一个节点,以便继续处理下一个要反转的节点
        end = begin->next;
    }
    //返回新的链表头节点head
    return head;
}

6.栈

思路

利用一个模拟栈的结构来暂存链表中的节点,然后再从栈中依次取出节点重新构建链表,从而实现链表的反转。

具体操作
  • 先处理特殊情况:链表为空或只有一个节点,直接返回head
  • 计算链表长度,创建一个结构体指针数组stack模拟栈,将链表每个节点依次压入栈。
  • 先取出栈顶节点(也就是原链表的最后一个节点),将其赋值给 ans,这个节点将作为反转后链表的头节点。同时,将临时指针 temp 也初始化为 ans,用于后续在构建反转链表过程中连接其他从栈中取出的节点。
  • 然后从栈中依次取出其他节点并连接到反转链表中。
  • 最后将最后一个节点的next指向NULL,就反转完成了。直接返回ans即可,如果是用malloc动态分配的内存,别忘了手动释放stack
AC代码(附注释)
struct ListNode* reverseList(struct ListNode* head) {
    //处理特殊情况:链表为空或只有一个节点,直接返回head
    if (head == NULL || head->next == NULL) {
        return head;
    }
    //使用typedef将struct ListNode重命名为Node,方便后续代码书写
    typedef struct ListNode Node;
    //计算原链表大小
    int size = 0;
    Node* temp = head;
    while (temp != NULL) {
        size++;
        temp = temp->next;
    }
    //根据链表节点个数动态分配内存,创建一个数组来模拟栈,用于存储链表节点指针
    Node** stack = (Node**)malloc(sizeof(Node*) * size);
    //栈顶指针,初始化为 -1,表示栈为空的初始状态
    int top = -1;
    //将链表的每个节点依次压入栈中
    while (head != NULL) {
        stack[++top] = head;
        head = head->next;
    }
    //从栈中取出栈顶节点(原链表的最后一个节点)作为反转后链表的头节点
    Node* ans = stack[top--];
    //从栈中依次取出其他节点并连接到反转链表中
    temp = ans;
    while (top >= 0) {
        temp->next = stack[top--];
        temp = temp->next;
    }
    //确保反转后链表的最后一个节点的next指针为NULL,形成正常的单链结构
    temp->next = NULL;
    //释放之前为存储链表节点指针而动态分配的栈内存空间,避免内存泄漏
    free(stack);
    //返回反转后链表的头节点
    return ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值