23.翻转链表

49 阅读2分钟

题目链接

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

解法1 暴力解法

思路

因为是单向链表,所以只能一直往前走。

这样可以遍历整个链表缓存下来,然后再从后面开始遍历修改 next 指向。

代码

function reverseList(head: ListNode | null): ListNode | null {
    if (!head) {
        return null;
    }
    let cur = head;
    const nodeList = []; // 缓存链表
    while (cur) {
        nodeList.push(cur);
        cur = cur.next;
    }
    const n = nodeList.length - 1;
    for (let i = n; i >= 0; i --) {
        nodeList[i].next = i === 0 ? null : nodeList[i - 1];
    }
    return nodeList[n];
};

时空复杂度分析

时间复杂度:一层循环。虽然遍历了两遍,但依然是 O(n)

空间复杂度:使用了数组额外缓存整个链表所以是 O(n)

解法2 原地修改指针

思路

我们可不可以在一次遍历的过程中就修改指针呢?

首先如果修改当前 nodenext 指向,那么肯定遍历不到 next 节点,所以在修改当前 node 的指针时,我们需要将 next 节点给保存下来,然后再将当前 node 指向之前的 next 节点。

其次修改完的链表最后应该是 null 节点。 最后就是修改指针的顺序很重要,需要先保存 cur.next 节点,然后就可以去修改 curnext,指向上一次个节点 pre。然后移动 curpre 指针。

image.png

代码

function reverseList(head: ListNode | null): ListNode | null {
    if (!head) {
        return null;
    }
    let cur = head;
    let pre = null;

    while (cur) {
        let next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }

    return pre;
};

时空复杂度

时间复杂度: 一次遍历 O(n)

空间复杂度:仅使用2个变量 O(1)

解法3 递归

思路

递归先思考终止条件:当前节点为空或没有下一个节点。

按照上图,终止条件为当前 head 为4,而递归回来的节点是 5,这时候需要修改 4 -> 5 为 5 -> 4。

所以 head.next(这是节点5).next 修改为 head 自身,然后切断当前 4 -> 5 的指向。

最后返回递归回来的节点。

代码

function reverseList(head: ListNode | null): ListNode | null {
    if (!head || !head.next) {
        return head;
    }
    const newHead = reverseList(head.next);
    head.next.next = head; // 将当前节点的下一个节点指向当前节点
    head.next = null; // 断开原有指针
    return newHead;
};

时空复杂度

时间复杂度:O(n)

空间复杂度:O(n),主要是递归调用栈空间消耗