目录
3169
16 分钟
单链表反转:迭代与递归全解析(整个 / 前 N 个 / 区间 / K 个一组)

概述#

反转单链表是链表类算法题的「地基」,几乎所有链表的进阶操作(回文判断、K 个一组反转、部分反转)都以它为基础。

反转的本质只有一句话:把每条 next 指针的方向反过来

反转前:1 → 2 → 3 → 4 → null
反转后:4 → 3 → 2 → 1 → null

围绕「反转」这一核心,本文按难度递进讲四个经典问题:

#问题对应题目难度
1反转整个链表LeetCode 206简单
2反转前 N 个节点206 的变体中等
3反转链表的一部分(区间)LeetCode 92中等
4K 个一组反转链表LeetCode 25困难

每个问题都给出迭代递归两种解法。掌握后你会发现:后三个问题其实都是第一个问题的「套壳」。

前置知识

  • 链表节点:只能沿 next 单向前进
  • 指针重连:反转的本质是修改引用
  • 递归:理解「后序遍历」式的处理顺序

链表节点定义#

本文所有代码共用下面的单链表节点结构:

C
typedef struct ListNode {
    int val;
    struct ListNode *next;
} ListNode;
C++
struct ListNode {
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};
JavaScript
class ListNode {
    constructor(val, next) {
        this.val = (val === undefined ? 0 : val);
        this.next = (next === undefined ? null : next);
    }
}

核心思想:迭代 vs 递归#

迭代:三指针#

迭代解法用三个指针 prevcurrnext,每一轮重复四步:

初始:prev = null, curr = head

null   1 → 2 → 3 → 4 → null
 ^     ^
prev  curr

每一轮:
  1. next = curr.next   // 先保存后继,防止断链
  2. curr.next = prev   // 当前节点反向指向
  3. prev = curr        // prev 前移
  4. curr = next        // curr 前移

循环结束的条件是 curr 走到 null,此时 prev 正好指向反转后的新头节点。

关键点:第 1 步「先保存 next」绝不能省,否则 curr.next 被改后,原本的后续链表就再也找不回来了。

递归:后序遍历#

递归解法的精髓是「先反转后面的,再处理当前的」,本质上是链表的后序遍历:

reverseList(1→2→3→4)
  = 先递归反转后面的:reverseList(2→3→4)  →  4→3→2
    再让「2」指向「1」:head.next.next = head
    最后断开「1」的旧指向:head.next = null

核心只有两行:

head.next.next = head   // 让后继节点反向指向自己
head.next = null        // 断开原来的正向指向

递归的基准情况head 为空或 head.next 为空(只剩一个节点,无需反转),此时直接返回 head

四大经典问题#

1. 反转整个链表(LeetCode 206)#

思路(迭代):三指针从头走到尾,见上文。

思路(递归):先反转 head.next 开始的子链,再让 head.next 指向 head,最后断开 head.next

输入:1 → 2 → 3 → 4 → 5 → null
输出:5 → 4 → 3 → 2 → 1 → null

2. 反转前 N 个节点#

思路(迭代):只反转前 n 个,反转结束后把原头节点(此时已是反转段的尾)接到剩余链上。

反转前:1 → 2 → 3 → 4 → 5 → null,n = 3
反转后:3 → 2 → 1 → 4 → 5 → null
                          └─ 剩余部分原样保留

思路(递归):与「反转整个」几乎一样,唯一区别是反转结束后,尾节点要接到n+1 个节点上。为此引入一个后继指针 successor,在递归到 n == 1 时记录下「第 n 个节点的后继」,并在回溯时把尾节点的 next 指向它。

successor 正是「反转前 N 个」能过渡到「区间反转」的关键桥梁。

3. 反转链表的一部分(区间 [left, right],LeetCode 92)#

思路(迭代):用虚拟头节点 dummy 定位到 left 的前驱 pre,然后对 [left, right] 这段做「头插法」反转——每次把 curr.next 摘下来插到 pre 后面,共执行 right - left 次。

输入:1 → 2 → 3 → 4 → 5,left = 2, right = 4
输出:1 → 4 → 3 → 2 → 5
           └─ [2,3,4] 被反转 ─┘

思路(递归):递归地缩小问题规模。当 left == 1 时,区间反转就退化为「反转前 right 个」;否则不断移动头指针,把 leftright 同时减 1 向深处递归,回来时把当前头接回去。

4. K 个一组反转链表(LeetCode 25)#

思路(迭代):用 preend 两个指针扫描,每次先把 end 前进 k 步「探路」:

  • 若剩余不足 k 个,直接结束;
  • 否则记录本组起点 start 和下一组起点 next,把本组截断后整体反转,再接回原链。

思路(递归):先判断剩余是否够 k 个,不够则原样返回;够则反转前 k 个(复用「反转前 N 个」),再递归处理剩余部分并接回。

输入:1 → 2 → 3 → 4 → 5,k = 2
输出:2 → 1 → 4 → 3 → 5
      └ 反转 ┘ └ 反转 ┘ └ 不足 k 个,原样 ┘

代码实现#

每种语言都包含四个问题 × 两种解法共 8 个函数,按注释分区。递归「反转前 N 个」用到的 successor 是模块级(文件级)变量。

C
#include <stddef.h>

typedef struct ListNode {
    int val;
    struct ListNode *next;
} ListNode;

/* ============ 1. 反转整个链表 ============ */

/* 迭代 */
ListNode* reverseList(ListNode* head) {
    ListNode* prev = NULL;
    ListNode* curr = head;
    while (curr) {
        ListNode* next = curr->next; /* 先保存后继 */
        curr->next = prev;           /* 反转当前指向 */
        prev = curr;                 /* prev 前移 */
        curr = next;                 /* curr 前移 */
    }
    return prev;
}

/* 递归 */
ListNode* reverseListRecursive(ListNode* head) {
    if (head == NULL || head->next == NULL) return head;
    ListNode* last = reverseListRecursive(head->next);
    head->next->next = head; /* 后继指向自己 */
    head->next = NULL;       /* 断开原指向 */
    return last;
}

/* ============ 2. 反转前 N 个节点 ============ */

/* 迭代 */
ListNode* reverseN(ListNode* head, int n) {
    ListNode* prev = NULL;
    ListNode* curr = head;
    for (int i = 0; i < n && curr; i++) {
        ListNode* next = curr->next;
        curr->next = prev;
        prev = curr;
        curr = next;
    }
    head->next = curr; /* 原头节点接上剩余部分 */
    return prev;
}

/* 递归(借助后继指针 successor) */
static ListNode* successor = NULL;

ListNode* reverseNRecursive(ListNode* head, int n) {
    if (n == 1) {
        successor = head->next; /* 记录第 n+1 个节点 */
        return head;
    }
    ListNode* last = reverseNRecursive(head->next, n - 1);
    head->next->next = head;
    head->next = successor; /* 反转后的尾节点接上后继 */
    return last;
}

/* ============ 3. 反转链表的一部分(区间 [left, right]) ============ */

/* 迭代(头插法) */
ListNode* reverseBetween(ListNode* head, int left, int right) {
    ListNode dummy;
    dummy.next = head;
    ListNode* pre = &dummy;
    for (int i = 1; i < left; i++) pre = pre->next; /* 定位到 left 前驱 */

    ListNode* curr = pre->next;
    for (int i = 0; i < right - left; i++) {
        ListNode* next = curr->next;
        curr->next = next->next;
        next->next = pre->next;
        pre->next = next;
    }
    return dummy.next;
}

/* 递归(退化为反转前 right 个) */
ListNode* reverseBetweenRecursive(ListNode* head, int left, int right) {
    if (left == 1) {
        return reverseNRecursive(head, right);
    }
    head->next = reverseBetweenRecursive(head->next, left - 1, right - 1);
    return head;
}

/* ============ 4. K 个一组反转 ============ */

/* 迭代 */
ListNode* reverseKGroup(ListNode* head, int k) {
    ListNode dummy;
    dummy.next = head;
    ListNode* pre = &dummy;
    ListNode* end = &dummy;

    while (end->next) {
        for (int i = 0; i < k && end; i++) end = end->next; /* 探路 */
        if (!end) break; /* 不够 k 个,结束 */

        ListNode* start = pre->next;
        ListNode* next = end->next;      /* 记录下一组起点 */
        end->next = NULL;                /* 截断本组 */
        pre->next = reverseList(start);  /* 反转本组 */
        start->next = next;              /* 接回后续 */
        pre = start;                     /* pre 指向本组末尾 */
        end = pre;                       /* 重置 end */
    }
    return dummy.next;
}

/* 递归 */
ListNode* reverseKGroupRecursive(ListNode* head, int k) {
    ListNode* end = head;
    for (int i = 0; i < k; i++) {
        if (!end) return head; /* 不够 k 个,保持原样 */
        end = end->next;
    }

    ListNode* newHead = reverseN(head, k);               /* 反转前 k 个 */
    head->next = reverseKGroupRecursive(end, k);         /* 递归处理剩余 */
    return newHead;
}
C++
struct ListNode {
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

class Solution {
public:
    /* ============ 1. 反转整个链表 ============ */

    ListNode* reverseList(ListNode* head) {
        ListNode* prev = nullptr;
        ListNode* curr = head;
        while (curr) {
            ListNode* next = curr->next;
            curr->next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }

    ListNode* reverseListRecursive(ListNode* head) {
        if (!head || !head->next) return head;
        ListNode* last = reverseListRecursive(head->next);
        head->next->next = head;
        head->next = nullptr;
        return last;
    }

    /* ============ 2. 反转前 N 个节点 ============ */

    ListNode* reverseN(ListNode* head, int n) {
        ListNode* prev = nullptr;
        ListNode* curr = head;
        for (int i = 0; i < n && curr; i++) {
            ListNode* next = curr->next;
            curr->next = prev;
            prev = curr;
            curr = next;
        }
        head->next = curr;
        return prev;
    }

    ListNode* reverseNRecursive(ListNode* head, int n) {
        if (n == 1) {
            successor = head->next;
            return head;
        }
        ListNode* last = reverseNRecursive(head->next, n - 1);
        head->next->next = head;
        head->next = successor;
        return last;
    }

    /* ============ 3. 反转链表的一部分 ============ */

    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode dummy(0, head);
        ListNode* pre = &dummy;
        for (int i = 1; i < left; i++) pre = pre->next;

        ListNode* curr = pre->next;
        for (int i = 0; i < right - left; i++) {
            ListNode* next = curr->next;
            curr->next = next->next;
            next->next = pre->next;
            pre->next = next;
        }
        return dummy.next;
    }

    ListNode* reverseBetweenRecursive(ListNode* head, int left, int right) {
        if (left == 1) {
            return reverseNRecursive(head, right);
        }
        head->next = reverseBetweenRecursive(head->next, left - 1, right - 1);
        return head;
    }

    /* ============ 4. K 个一组反转 ============ */

    ListNode* reverseKGroup(ListNode* head, int k) {
        ListNode dummy(0, head);
        ListNode* pre = &dummy;
        ListNode* end = &dummy;

        while (end->next) {
            for (int i = 0; i < k && end; i++) end = end->next;
            if (!end) break;

            ListNode* start = pre->next;
            ListNode* next = end->next;
            end->next = nullptr;
            pre->next = reverseList(start);
            start->next = next;
            pre = start;
            end = pre;
        }
        return dummy.next;
    }

    ListNode* reverseKGroupRecursive(ListNode* head, int k) {
        ListNode* end = head;
        for (int i = 0; i < k; i++) {
            if (!end) return head;
            end = end->next;
        }
        ListNode* newHead = reverseN(head, k);
        head->next = reverseKGroupRecursive(end, k);
        return newHead;
    }

private:
    ListNode* successor = nullptr; /* 递归「反转前 N 个」用到的后继指针 */
};
JavaScript
class ListNode {
    constructor(val, next) {
        this.val = (val === undefined ? 0 : val);
        this.next = (next === undefined ? null : next);
    }
}

// 递归「反转前 N 个」用到的后继指针
let successor = null;

/* ============ 1. 反转整个链表 ============ */

function reverseList(head) {
    let prev = null;
    let curr = head;
    while (curr) {
        const next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}

function reverseListRecursive(head) {
    if (!head || !head.next) return head;
    const last = reverseListRecursive(head.next);
    head.next.next = head;
    head.next = null;
    return last;
}

/* ============ 2. 反转前 N 个节点 ============ */

function reverseN(head, n) {
    let prev = null;
    let curr = head;
    for (let i = 0; i < n && curr; i++) {
        const next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    head.next = curr;
    return prev;
}

function reverseNRecursive(head, n) {
    if (n === 1) {
        successor = head.next;
        return head;
    }
    const last = reverseNRecursive(head.next, n - 1);
    head.next.next = head;
    head.next = successor;
    return last;
}

/* ============ 3. 反转链表的一部分 ============ */

function reverseBetween(head, left, right) {
    const dummy = new ListNode(0, head);
    let pre = dummy;
    for (let i = 1; i < left; i++) pre = pre.next;

    const curr = pre.next;
    for (let i = 0; i < right - left; i++) {
        const next = curr.next;
        curr.next = next.next;
        next.next = pre.next;
        pre.next = next;
    }
    return dummy.next;
}

function reverseBetweenRecursive(head, left, right) {
    if (left === 1) {
        return reverseNRecursive(head, right);
    }
    head.next = reverseBetweenRecursive(head.next, left - 1, right - 1);
    return head;
}

/* ============ 4. K 个一组反转 ============ */

function reverseKGroup(head, k) {
    const dummy = new ListNode(0, head);
    let pre = dummy;
    let end = dummy;

    while (end.next) {
        for (let i = 0; i < k && end; i++) end = end.next;
        if (!end) break;

        const start = pre.next;
        const next = end.next;
        end.next = null;
        pre.next = reverseList(start);
        start.next = next;
        pre = start;
        end = pre;
    }
    return dummy.next;
}

function reverseKGroupRecursive(head, k) {
    let end = head;
    for (let i = 0; i < k; i++) {
        if (!end) return head;
        end = end.next;
    }
    const newHead = reverseN(head, k);
    head.next = reverseKGroupRecursive(end, k);
    return newHead;
}

复杂度与解法对比#

问题迭代时间迭代空间递归时间递归空间
反转整个链表O(n)O(1)O(n)O(n)(递归栈)
反转前 N 个O(n)O(1)O(n)O(n)
区间反转O(n)O(1)O(n)O(n)
K 个一组O(n)O(1)O(n)O(n/k)

迭代 vs 递归

  • 迭代:空间 O(1),无栈溢出风险,工程首选。
  • 递归:代码更简洁、语义更直观,但每次调用都占用栈空间,n 很大时可能栈溢出;其价值更多在于「思路迁移」——reverseN + successor 的写法能干净地推出区间反转和 K 组反转。

关键要点#

  1. 反转前先保存 next:这是迭代解法的命门,忘记保存就会断链。
  2. head.next.next = head 是递归的灵魂:一句「让后继指向自己」完成了反向重连。
  3. successor 是「前 N 个」到「区间」的桥梁:记录「第 n+1 个节点」,让反转段能正确接回剩余部分。
  4. 区间反转 = 定位前驱 + 头插法:dummy 节点统一了 left = 1 的边界情况。
  5. K 组反转先「探路」:判断剩余是否够 k 个,不够则原样保留,这是它与「整个反转」的唯一区别。

扩展思考#

  • 回文链表(LeetCode 234):先快慢指针找中点,再反转后半段,最后双指针比较——正是「反转」+「双指针」的组合拳。
  • 递归栈风险:链表长度到 10^5 时,递归深度可能超出默认栈上限,此时应改用迭代。
  • 反转整个链表还有一种「尾插法」写法(每轮把 curr 插到 dummy 之后),与区间反转的头插法一脉相承,可以自行尝试。

相关文章#

单链表反转:迭代与递归全解析(整个 / 前 N 个 / 区间 / K 个一组)
https://www.hehonglei.cn/posts/linked-list-reverse/
作者
Honglei He
发布于
2026-09-01
许可协议
CC BY-NC-SA 4.0