# 反转链表 (opens new window)

  • 难度:Easy
  • 标签:链表反转 递归 尾递归

# 刷题思路

  • [x] 迭代法
  • [x] 自递归法 -- 反转后尾接
  • [x] 尾递归法
  • [x] 特殊解法:reduce 构造链表

# 方法 1 迭代法

  • 思路: 创建res存储"已完成反转的结果" & 推进指针curr,推进直到curr为空,返回res.
  • 复杂度:
    • 时间 O(N): 一次遍历
    • 空间 O(1): 常数级额外空间故 O(1).
  • Leetcode 结果:
    • 执行用时: 60 ms, 在所有 JavaScript 提交中击败了 99 %的用户
    • 内存消耗: 34.9 MB, 在所有 JavaScript 提交中击败 51 %的用户
var reverseList = function(head) {
    let [res, curr] = [null, head] // res存储结果, curr推进
    while (curr) {
        const next = curr.next  // 1. 临时存储当前节点后续内容
        curr.next = res         // 2. 反转链表,当前节点指向"已完成反转的结果"
        res = curr              // 3. 存储"最新完成反转的结果"
        curr = next             // 4. 接回临时存储的后续内容, 当前节点继续推进

        // 简化: 以上四行等价于下面一行
        // [curr.next, res, curr] = [res, curr, curr.next]
    }
    return res
};

# 方法 2 自递归法 -- 反转后尾接

  • 思路: 自递归无法存储推进状态所以无法尾递归,不断将 next 放入递归方法反转链表,结果.next = 当前节点. (Tip: 记得推进结果直到 next.next 为空)
  • 复杂度:
    • 时间 O(N): 每层时间复杂度均为 O(1), 总共 n-1 层递归,故时间复杂度为 O(n).
    • 空间 O(N): 递归调用栈消耗空间,共 n-1 层递归,故空间复杂度为 O(n).
  • Leetcode 结果:
    • 执行用时: 68 ms, 在所有 JavaScript 提交中击败了 85 %的用户
    • 内存消耗: 35.2 MB, 在所有 JavaScript 提交中击败 24 %的用户
var reverseList = function(head) {
    // if (!head || !head.next) return head
    // const next = head.next // 当前节点的后续部分将被反转,next节点反转后成为"反转部分的尾节点"
    // const reverseHead = reverseList(next)
    // head.next = null // 通过剪裁使当前节点成为"末节点"
    // next.next = head // 把"末节点"尾接到"反转部分的尾节点"之后
    // return reverseHead

    // 简化如下:
    if (!head || !head.next) return head
    const reverseHead = reverseList(head.next) // 反转链表
    head.next.next = head // 把"末节点"尾接到"反转部分的尾节点"之后
    head.next = null // 通过剪裁使当前节点成为"末节点"
    return reverseHead
};

# 方法 3 尾递归法

  • 思路: 用 res 和 curr 存储推进状态,直到 curr 为空则输出结果.

  • 复杂度:

    • 时间: O(n): 等同于正常推进,故 O(n).
    • 空间: O(1): 尾递归方式,重复使用一个空间故空间复杂度为 O(1).
  • 结果:

    • 执行用时: 60 ms, 在所有 JavaScript 提交中击败了 98 %的用户
    • 内存消耗: 35.2 MB, 在所有 JavaScript 提交中击败 27 %的用户
    var reverseList = function(head) {
        return reverse(null, head)
    };
    
    function reverse (res, curr) {
        if (!curr) return prev
        let tmp = curr.next
        curr.next = res
        return reverse(curr, tmp)
    }
    

# 方法 4 特殊解法:reduce 构造链表

  • 思路: 转数组后 reduce 构造出链表
  • 模仿自: 转换成对应的数字直接相加 (opens new window)
  • 复杂度:
    • 时间: O(n): 两轮遍历.
    • 空间: O(1): 结果不算额外空间,但确实是非原地反转算法.
  • 结果:
    • 执行用时:76 ms, 在所有 JavaScript 提交中击败了87.14%的用户
    • 内存消耗:39.2 MB, 在所有 JavaScript 提交中击败了15.43%的用户
var reverseList = function(head) {
    if (!head || !head.next) return head
    const arr = []
    while (head) {
        arr.push(head.val)
        head = head.next
    }
    return arr.reduce((acc, v) => { return { val: v, next: acc } }, null)
};

JS刷题记录 Leetcode-js (opens new window) 每周都会更新刷题心得或者题解, 你的点赞或 star 都将助力我产出更好内容~