LintCode T35 翻转链表

179 阅读5分钟

考察内容:单链表,递归

时间:2020-09-01 星期二

作者:guuzaa

掘金主页:🌏

题目描述 📃

题目链接 🔗

这算是一道正儿八经的数据结构题,学过数据结构的同学应该都有了解。题目要求很简单就是翻转链表。

分析 💻

链表有点类似于生活中的排队,除了第一和最后的人之外,其他人前后都有人。翻转链表就相当于是将这个队伍翻转。很容易想到的是,从队首开始,每个人依次向后转,就能翻转队伍。下面我从两个角度分别实现这个方法。

迭代解法

每个人依次向后转,放到链表中就是将每个节点指针域指向前置节点。

落实到代码中,我们假设存在一个 cur 节点,这个节点前的节点都已经成功翻转,它的后置节点 nxt 还没有进行翻转。那么初始时, cur 节点 nullnxt指向头指针。之后不断遍历每个节点,将 nxt 节点指针指向它的前置,即 cur。操作完成后,nxtcur 向后移动直到 nxt 为空,而cur移动到链表尾,返回 cur即可。

C++ 代码
ListNode *reverse(ListNode *head) {
    ListNode *cur = nullptr, *nxt = head;
    
    while(nxt != nullptr) {
        ListNode *dummy = nxt->next;  // 暂存nxt的后置
        nxt->next = cur; // nxt 节点指针指向前置
        cur = nxt; // cur nxt 向后移动
        nxt = dummy;
    }
    
    return cur;
}

看完这个代码之后,发现开 nxt 节点是多余的,只需用头指针 head 作为工作指针就能解决这道题目。

// 精简版
ListNode *reverse(ListNode *head) {
    ListNode *cur = nullptr;
    while(head != nullptr) {
        ListNode *dummy = head->next;
        head->next = cur;
        cur = head;
        head = dummy;
    }
    return cur;
}
时空复杂度分析

因为是一次遍历,所以时间复杂度是 O(N) 。就地翻转,因此空间复杂度是 O(1)

递归解法

说解法之前,先复习一下递归。

递归就是反复调用自身,每次调用时会把问题规模缩小,直到规模缩小到可以得到结果(递归出口),然后再返回上层的调用求除对应解。

说实话,我起初也只想到了迭代解法,递归也能做我是真的没有想到。经过后来分析之后,发现还真的有递归的特点。如果要翻转一条 head 指向的链表,那么我们先翻转 head 后置节点指向的链表,之后将 head 后置节点的指针指向 head 就可以了呀。递归出口也很容易想到,如果链表只有一个节点就返回该节点。所以有了下面的代码:

Python 代码
def reverse(self, head):
    # 空指针检查
    if not head or head.next is None:
        return head

    reverse_list = self.reverse(head.next)
    head.next.next = head

    return reverse_list

但是 TLE 超时了,那么说明递归体有问题产生了死循环。继续分析,发现头指针跟它后置节点之间产生了环。怎么解决呢?

正常情况下,翻转后链表的原头指针的指针域应该为 null ,检查自己的代码发现没有这一步操作。那么怎样才能使头指针的指针域指向 null呢?

我的方法是,在进行递归调用之前,先将链表头指针的后置置为 null ,考虑到后面的递归需要用到链表头指针的后置所以开一个指针暂时存储它。下面是代码实现:

Python 代码
def reverse(self, head):
    if not head or head.next is None:
        return head
    nxt, head.next = head.nextNone  # nxt 暂存, 链表头指针后置置为 null
    
    reverse_list = self.reverse(nxt)
    nxt.next = head
    return reverse_list

能不能再简单一点?可不可以将链表头指针后置节点置空操作放到递归后呢?

递归图解
递归图解

像上面的图那样,假设某一时刻 head 的后置节点指针指向了 head,直接把 head.next 设为空,因为上层调用不再需要 head.next了。这样便解决了头指针跟它后置节点之间产生环的问题。最终代码如下:

def reverse(self, head):
    if not head or head.next is None:
        return head
    
    reverse_list = self.reverse(head.next)
    head.next.next = head
    head.next = None
    
    return reverse_list
时空复杂度分析

跟迭代解法相同,时间复杂度是 O(N),空间复杂度是 O(1)

总结 📕

  • 我是2020级的准研究生,这道题目是我复试考到的题目,当时考试的时候我写的很烂。
  • 递归一直是我的软肋。今天写的时候还是遇到了死循环的情况,还是需要继续加油。

全文完