《剑指 Offer》学习记录:题 24:反转链表

题 24:反转链表

题干

定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。——《剑指 Offer》P142

测试样例

链表的数据结构定义如下(Python):

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

若传入的头结点 head 指向的链表结构如图所示:

则返回的头结点 head 指向的链表结构为:

迭代法

方法思路

想要翻转链表,其实就是修改每个结点的指针域,使其指向该结点的前驱。修改时,对于每个结点而言有 2 个点需要格外注意,其一是保存每个结点的前驱,第二是记录结点的后继,防止丢失信息而无法访问后续的结点。对于整张链表而言,需要注意链表翻转之后,原本的首元结点的后继应该为 NULL,而原本的尾结点将成为新的首元结点。

方法模拟

例如有如下的链表,使用一个指针 ptr 指向首元结点,pre 指向该结点的前驱(首元结点没有前驱,因此指向 NULL),nex 指向 ptr->next。使用 3 个指针记录是为了在修改结点的前驱、后继关系时,能够做到不丢信息。

在记录 3 个结点的信息之后,可以把 ptr 的后继修改为其前驱 pre。

修改该结点的后继关系后,需要把 ptr 和 pre 往后移动,这样才能继续处理剩余的结点。因为 ptr 指针的信息还在,可以直接把 pre 移动到 ptr 的位置上。

接着把 ptr 移动到 nex 的结点上:

令 nex = ptr->next,就可以更新 nex 指针。经过对原来的首元结点进行调整,现在的链表状态如下,重复上述操作即可实现翻转链表。

解题代码

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        ptr = head    #ptr 指向首元结点
        pre = None    #pre 初始化为 NULL
        
        while ptr:
            nex = ptr.next    #修改 ptr 的后继
            ptr.next = pre    #将 ptr 和 pre 向后移动
            pre = ptr
            ptr = nex
        return pre

时空复杂度

设链表的结点数为 n,算法的流程需要遍历整张链表,因此时间复杂度为 O(n)。
由于只需要 3 个指针存储信息,而不需要其他辅助空间,空间复杂度为 O(1)。

递归法

从题解——【反转链表】:双指针,递归,妖魔化的双指针中学习到的。

方法思路

递归法是一种巧妙的方法,充分利用了递归回溯的功能。由于链表不支持随机访问,因此想要访问第 i 个结点,就需要先访问第 i-1 个结点。如果将思路逆转过来,翻转链表可以视为将原来的尾结点当做头结点,做尾插法。此时的关键点有 2 个,其一是如何将原来的尾结点当做首元结点返回,其二是如何反向取到每一个结点。

方法模拟

这时可以充分利用递归,首先递归遍历整个链表,递归函数每次传入结点 ptr 的后继 ptr-> next。

当遍历到尾结点时开始回溯,此时递归函数的返回值是尾结点,因为这样就类似于接力棒,可以把尾结点作为翻转后的链表的头结点传回来。至于如何实现“尾插法”,注意此时递归的层次已经回到了尾结点的前驱,因此可以通过 ptr->next 取到当前递归层次所在的结点的后继,这样就可以进行修改。

再次递归回溯,就可以继续取到前面的结点了,继续 ptr->next 取到当前结点的后继进行操作。不断重复上述操作,即可完成链表逆序。

题解代码

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        #遍历完所有结点作为递归出口
        if(head == None or head.next == None):
            return head    #返回尾结点
        
        tail = self.reverseList(head.next)    #接收返回的尾结点
        head.next.next = head    #修改 head->next->next 的指向
        head.next = None
        return tail    #仍然返回尾结点,向前传递

时空复杂度

设链表的结点数为 n,算法的流程需要遍历整张链表,因此时间复杂度为 O(n)。
由于递归的深度和结点数 n 相同,保留递归的状态需要额外的空间,空间复杂度为 O(1)。

参考资料

《剑指 Offer(第2版)》,何海涛 著,电子工业出版社
【反转链表】:双指针,递归,妖魔化的双指针

posted @ 2021-04-16 21:13  乌漆WhiteMoon  阅读(97)  评论(0编辑  收藏  举报