算法与数据结构 · 实战

LC:反转链表I

本文写于 2021 年,涉及的组件版本、配置与数据可能已过时,请按经验材料阅读并核对当前版本。原文链接与内容保持不变。

题目

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

示例一

输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]

示例二

输入:head = [1,2] 输出:[2,1]

示例三

输入:head = [] 输出:[]

提示:

链表中节点的数目范围是 [0, 5000] -5000 <= Node.val <= 5000  

进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?

相关链接

分析

这是一道非常简单的题目。也是很容易出现达克效应的一道题目。 为什么呢? 应为这道题太简单了啊。 思路 太简单了啊。 但是真正去写的话,可不见得真有那么简单。

简单的题目,我们还是 以图的形式来展示。

思路一: 迭代链表, 创建新节点,重组成新链表

思路解析

一图胜千言

核心思路如标题。 首先将遍历的原链表中的节点值复制到新建的节点中(a步骤),节点next指针指向是上一个节点,没有则为null(b步骤). 遍历链表的所有节点。

这样其实会生成一个新的链表。也就说会同时存在两条链表。

[:fist_raised:时间复杂度为 O(n), 空间复杂度为 O(n)]{.label .success}

代码实现

public class Solution {

    public ListNode reverseList(ListNode head) {

        // 非空判断
        if (null == head || null == head.next) {
            return head;
        }

        // 遍历旧链表,新建节点,并且拼接成新链表。
        ListNode pre = null;
        while (null != head) {
            ListNode curr = new ListNode(head.val);
            curr.next = pre;
            pre = curr;
            head = head.next;
        }
        return pre;
    }
}

思路二: 迭代变更链表的指针指向

思路解析

这种思路也是比较简单的,但是一定要想好喽之后,再看开始编码,要不然很容易思绪错乱。

图解:

一图胜千言

这种解法需要两个指针, pre 代表其前一个节点, cur 代表后一个节点。进行遍历,从而实现 指针指向的变更。 当然,还需要一个临时节点去帮忙 pre 和 cur 指针,向后移动。

[:fist_raised:时间复杂度是 O(n);空间复杂度是 O(3);]{.label .success}

代码实现

public class Solution2 {

    public ListNode reverseList(ListNode head) {
        // 非空判断
        if (null == head || null == head.next) {
            return head;
        }
        ListNode pre = head, cur = head.next;
        // !!注意:断开头节点的next指针,避免产生环型链表。
        pre.next = null;
        while (null != cur) {
            ListNode tempNode = cur.next;
            cur.next = pre;
            pre = cur;
            cur = tempNode;
        }
        return pre;
    }
}

思路三: 递归方式, 变更指针

思路解析

这思路三,其实是思路二的不同写法。 这种实现方法首先就需要将 链表 “递” 到底,然后在归的时候,把 节点链接到一起。如下图

一图胜千言

这样说起来,递归这种方式来解决反转类的问题好像会有一种与生俱来的优势。 首先递进去,然后归出来。

代码实现

public class Solution3 {

    public ListNode reverseList(ListNode head) {
        // 非空判断
        if (null == head || null == head.next) {
            return head;
        }

        // 递归入口
        return reverse(head, null);
    }

    public ListNode reverse(ListNode currNode, ListNode preNode) {
        ListNode headNode = null;
        if (null == currNode) {
            headNode = preNode;
        } else {
            headNode = reverse(currNode.next, currNode);
            currNode.next = preNode;
        }
        return headNode;
    }
}

思考

这一道题考的是简单的逻辑问题,实属简单至极的题目。要注意 陷入 “达克效应”。

勿以题小而不做。

使用头递归来做反转类似的题目,好像是一种天然支持的特性。但是在实际的开发过程中,不建议过多的使用递归的方式,一方面不具备可读性。另一方面,递归方式对性能不太优化,还有重要的是要考虑栈溢出的风险。

相关题目

相关链接

最后

期望与你一起遇见更好的自己

期望与你一起遇见更好的自己

配套资料 · 免费领取
本文属于「ALGO」资料包范围:算法系列合订 + 刷题清单。LeetCode 与杭电 OJ 的题解索引。
在公众号「方家小白」回复关键词 ALGO 即可领取。全文无需关注即可阅读。
ALGO