链表反转:从脱单到逆袭,程序员如何优雅“翻旧账”

47 阅读4分钟

翻转链表就像重新排列人生轨迹,需要一点技巧和勇气

大家好,我是你们的技术小伙伴FogLetter,今天我们来聊聊链表的反转——这个在面试中出现频率堪比“请自我介绍”的经典问题。

场景引入:当链表想要“重新来过”

想象一下,你有一条单向链表,就像一列单向行驶的小火车,每个车厢只能看到前面的车厢,却回不了头。但某天,链表突然想要“重新审视人生”,看看来时的路——这就是链表反转要解决的问题。

在真实世界中,链表反转的应用无处不在:撤销操作、回文判断、两数相加……掌握它,你就是那个能帮链表“翻旧账”的魔法师。

迭代法:一步一个脚印的翻转术

先来看迭代解法,这是最直观也最容易被面试官追问细节的方法:

function reverseList(head) {
    let prev = null;    // 前女友:已经是过去式
    let cur = head;     // 现任:当前正在处理的关系
    
    while (cur) {
        const next = cur.next;  // 备胎?不,是下一个要处理的对象
        cur.next = prev;        // 勇敢转身,面向过去
        prev = cur;             // 前女友更新为现任
        cur = next;             // 现任变成下一个目标
    }
    
    return prev;  // 最终,第一个变成最后一个,最后一个成为新开始
}

来,我们一步步拆解这个“情感关系重组过程”

假设我们有一条链表:A → B → C → null

第一轮循环:

  • prev = null, cur = A
  • 保存 next = B(记住下家)
  • A.next = null(A与过去切断联系)
  • prev = A(A成为新的“前任标准”)
  • cur = B(移情别恋到B)

此时链表状态:null ← A B → C → null

第二轮循环:

  • prev = A, cur = B
  • 保存 next = C
  • B.next = A(B回头指向A)
  • prev = B
  • cur = C

状态:null ← A ← B C → null

第三轮循环:

  • prev = B, cur = C
  • 保存 next = null
  • C.next = B(C回头指向B)
  • prev = C
  • cur = null

最终:null ← A ← B ← C

循环结束,返回 prev 即 C,成为新链表的头节点。

迭代法的精髓

  • 时间复杂度:O(n),每个节点只访问一次
  • 空间复杂度:O(1),只用了三个指针,堪称节约内存的典范
  • 核心思想:逐个翻转指针方向,像翻书一样一页页翻转链表

递归法:相信未来的自己

如果说迭代法是勤劳的蚂蚁,那么递归法就是相信"未来的自己一定能搞定"的智者:

function reverseListRecursive(head) {
    // 递归结束条件:空链表或只有一个节点
    if (!head || !head.next) {
        return head;
    }
    
    // 相信递归:后面的事情交给下一个我
    const newHead = reverseListRecursive(head.next);
    
    // 回溯时的魔法时刻
    head.next.next = head;  // 让下一个节点指向自己
    head.next = null;       // 斩断自己原来的指向
    
    return newHead;  // 新的头节点一路传递回来
}

递归的"信任链"是如何工作的?

还是以 A → B → C → null 为例:

递去阶段:

  1. reverse(A) 调用 reverse(B)
  2. reverse(B) 调用 reverse(C)
  3. reverse(C) 发现 C.next 为 null,返回 C

归来阶段:

从 C 返回给 B:

  • newHead = C
  • B.next.next = B 即 C.next = B(C指向B)
  • B.next = null(B切断原有指向) 现在:A → B ← C,返回 C

从 B 返回给 A:

  • newHead = C
  • A.next.next = A 即 B.next = A(B指向A)
  • A.next = null(A切断原有指向) 现在:A ← B ← C,返回 C

最终得到 C → B → A → null

递归法的哲学

  • 信任递推:我相信我调用的函数能解决子问题
  • 精致回溯:在回来的路上完成指针翻转
  • 空间代价:O(n)的栈空间,用空间换来了思维的简洁

两种方法的对比:不同场景下的选择

特性迭代法递归法
空间复杂度O(1)O(n)
代码可读性直观易懂抽象优雅
适用场景内存受限环境代码简洁优先
面试表现容易解释展现思维深度

实战小贴士

  1. 边界处理:空链表和单节点链表直接返回
  2. 指针安全:在修改 next 前一定要保存原值
  3. 递归深度:超长链表可能栈溢出,了解你的环境限制
  4. 多解法掌握:面试时先给迭代解,被追问再展示递归功力

思维延伸

链表反转其实体现了计算机科学中一个很重要的思想:局部操作可以产生全局效果。我们不需要一次性处理整个链表,只需要处理好当前节点与周边的关系,最终整个链表就会自然翻转。

这种思想在分布式系统、函数式编程中随处可见——做好本地正确的事,全局自然会正确。

总结

链表反转就像人生中的转折点,有时候需要勇敢地转身面向过去,才能开启新的方向。迭代法教会我们脚踏实地,递归法则告诉我们相信未来的力量。

下次面试遇到这个问题,希望你能自信地说:"这个问题我有两种理解方式……"

技术如人生,懂得何时坚持、何时转向,才是真正的智慧。