【链表OJ题】反转链表

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

引言

在这篇博客中,我们将深入探讨如何有效地反转单链表。通过图解和代码示例,我们将详细讲解两种主要的解决方法:迭代法和递归法。了解这些方法的实现原理和应用场景,不仅可以提高你的编码技能,还可以加深对链表及其操作的理解。无论你是正在为技术面试做准备,还是希望加强数据结构知识,本文都将为你提供清晰而深入的指导。让我们一同探索链表反转这个有趣而实用的主题,并深化对算法思维的认识。

反转链表理解

链表反转是一种基本的链表操作,其本质是改变节点之间的指针方向从原来的单向顺序变为相反的顺序。这个过程可以通过以下直观的理解来描绘:
1. 初始状态:首先,链表中的节点按顺序连接,每个节点的指针都指向其后一个节点。这种结构使得从头节点开始,我们可以依次访问所有节点。
2. 反转操作:在链表反转过程中,我们从头节点开始遍历,将
每个节点的指针方向逆转这意味着每个节点的指针将不再指向其后一个节点,而是指向其前一个节点。通过这种操作,链表的方向被完全颠倒。
3. 终止状态:最终,
原来的尾节点成为新的头节点,它的指针指向空值。而原来的头节点成为新的尾节点,它的指针指向其前一个节点。整个链表形成了一个倒序的结构。 这个过程可以形象地比喻为翻转一串珠子,原来从左到右排列的珠子变成了从右到左排列。

双指针迭代法

struct ListNode* reverseList(struct ListNode* head) 
{
    // 初始化两个指针,slow初始化为NULL,fast初始化为头节点
    struct ListNode* slow = NULL;
    struct ListNode* fast = head;

    // 如果链表为空,直接返回头节点
    if (fast == NULL)
        return head;

    // 循环遍历链表
    while (fast)
    {
        // 暂存当前节点的下一个节点
        struct ListNode* tmp = fast->next;

        // 将当前节点的指针反转,指向前一个节点
        fast->next = slow;

        // 更新 slow 和 fast 指针,继续遍历
        slow = fast;
        fast = tmp;
    }

    // 返回反转后的链表头节点
    return slow;
}

代码分析:
1. 初始化指针:通过初始化 slow 为 NULL,fast 为头节点 head,创建了两个指针用于在循环中遍历链表。特殊情况处理:通过检查 fast 是否为 NULL,处理了链表为空的情况,直接返回头节点 head。
2. 循环遍历:进入循环后,
使用 tmp 暂存当前节点 fast 的下一个节点,然后将当前节点的指针 fast->next 反转,指向前一个节点 slow。然后更新 slow 和 fast 指针,继续遍历。
3. 返回结果:循环结束后,返回 slow,它现在指向反转后链表的头节点。

时间复杂度:该算法的时间复杂度为 O(n),其中 n 是链表的长度,因为每个节点都需要被访问一次。

空间复杂度:该算法的空间复杂度为 O(1),因为只使用了常数级别的额外空间来存储几个指针。

递归法

struct ListNode* reverseList(struct ListNode* head) 
{
    if (head == NULL || head->next == NULL)
        return head;
    
    // 递归反转后续节点,得到新的头节点 tmp
    struct ListNode* tmp = reverseList(head->next);

    // 将当前节点的下一个节点的指针指向当前节点,实现反转
    head->next->next = head;
    
    // 断开当前节点与下一个节点的连接
    head->next = NULL;

    // 返回新的头节点 tmp
    return tmp;
}

代码分析:
1. 首先通过 if (head == NULL || head->next == NULL) 判断链表是否为空或只有一个节点,若是,则直接返回头节点。
2. 递归调用:通过 struct ListNode* tmp = reverseList(head->next); 递归反转后续节点,得到新的头节点 tmp。
3. 反转操作:head->next->next = head; 将当前节点的下一个节点的指针指向当前节点,实现反转。
4. 断开连接:head->next = NULL; 断开当前节点与下一个节点的连接。
5. 返回新头节点:返回递归调用的结果 tmp,它是原链表的尾节点,确保最终返回的是反转后链表的新头节点。 

时间复杂度:该算法的时间复杂度为 O(n),每一次递归调用都会处理链表中的一个节点,而递归的深度等于链表的长度。

空间复杂度:该算法的空间复杂度为 O(n),每一次递归调用都会在栈上创建一个新的函数栈帧用来保存局部变量和递归调用返回的地址。

递归法的思路相较于迭代法更为巧妙,但在实际应用中,递归的深度可能受到栈的限制,因此需要权衡使用。在理解了递归解法的基本思路后,可以对比两种方法的优劣势,选择适合具体情况的方法。

其他思路

这题还可以通过遍历整个链表,依次取节点下来头插,由于涉及到头为不为空的问题,因此我们可以创建一个虚拟头节点(哨兵位),每次在虚拟头节点的后面插入即可,最后返回虚拟头节点的下一个节点,不要忘记释放掉这个虚拟头节点哦。个人感觉这种方法不如双指针来的简单,就不进行讲解了,感兴趣的可以在力扣上试一试。

  • 19
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值