
1. 题目到底在考什么为什么面试官偏爱这道链表题很多人刷到“剑指 Offer 24. 反转链表”这道题的时候第一反应是这不就是一个链表反转嘛背个代码不就完了但实际上这道题在面试中的出现频率极高而且考察的绝不只是“会不会写”这么简单。它考察的是你对链表这种数据结构底层形态的理解、对指针操作的自然程度、对边界条件的敏感度以及——在C语言这个语境下——你对内存和节点连接关系是否有清晰的直觉。先看题目本身。输入是一个单链表的头节点指针输出是反转后链表的头节点指针。比如链表原来是1 - 2 - 3 - 4 - 5反转之后是5 - 4 - 3 - 2 - 1。看起来确实简单但越是简单的题目越容易暴露问题指针指向操作顺序错了几个节点之后整个链表就断了递归没想清楚终止条件导致栈溢出边界条件没覆盖空链表和单节点链表甚至有人在反转之后不知道如何把新的头节点传出去。我见过不少候选人数组题、字符串题都能写得很溜一到链表手写就开始犹豫——因为数组的逻辑是“下标 连续内存”你在纸上画格子就能推演而链表是“节点 指针”每个节点散落在内存各处你脑子里必须有一条清晰的“指针游走”画面。反转链表这道题本质上是逼你把那幅画面画出来三个指针怎么交替前进、每一次循环后链表状态变成什么样、循环终止时各指针落在哪个位置。如果你能不看任何参考代码在纸上把三指针迭代法的每一步画清楚那说明你对链表的理解已经合格了。这道题适合谁来刷坦白说所有准备技术面试的人都绕不开。不管你是面大厂还是中小公司链表反转几乎是一种“必须写对”的基本功。同时也适合刚学完C语言结构体和指针、想用实际题目练手的学生。C语言版本尤其值得多花时间它没有STL里现成的容器所有操作都发生在裸指针上你能真正感受到“链表反转到底动了什么”——答案是我们只改变了每个节点的next指针方向节点的内存地址完全没有变。2. 三大解法逐一拆解迭代、递归、头插法2.1 三指针迭代法最推荐必须吃透三指针迭代法是我在面试中首推的解法也是我认为所有解法里最“稳”的一个。它会用到三个指针变量pre、cur、next。初始状态pre置为NULLcur指向头节点head。接下来进入循环循环体内做的事情只有三行核心逻辑next cur-next; // 先保存下一个节点防止链表断掉 cur-next pre; // 把当前节点的 next 指向前一个节点完成反转 pre cur; // pre 前移 cur next; // cur 前移循环什么时候结束当cur遍历到链表末尾时也就是cur NULL时终止。此时pre指向的就是原链表的尾节点也就是反转后链表的头节点直接return pre即可。为什么一定要先保存next这是初学者最容易犯的致命错误。假如你先执行cur-next pre当前节点的 next 已经指向前方了你再想通过cur-next拿到“原来的下一个节点”就永远拿不到了——那个节点已经跟当前节点断开了。这就好比你要拆一列火车先把两节车厢的连接处断开了但断开之前你却没记住下一节车厢长什么样结果你就永远找不到剩下的车厢了。所以next必须提前保存。完整的代码实现如下#include stdio.h #include stdlib.h // 单链表节点定义 struct ListNode { int val; struct ListNode *next; }; // 三指针迭代法反转链表 struct ListNode* reverseList(struct ListNode* head) { struct ListNode *pre NULL; struct ListNode *cur head; struct ListNode *next NULL; while (cur ! NULL) { next cur-next; // 保存下一个节点 cur-next pre; // 反转指针方向 pre cur; // pre 前进 cur next; // cur 前进 } return pre; }空链表和单节点链表要不要单独处理从上面的代码看不需要。空链表时cur NULL循环一次都不执行直接返回NULL单节点链表时执行一次循环pre指向唯一节点cur变为NULL返回该节点。这两个边界情况被代码结构自然覆盖了这是迭代法的最大优势——边界处理毫不费力。2.2 递归解法理解函数语义是关键递归写法看起来极度精简但很多人写不出来原因是没想清楚递归函数的语义。reverseList(head)这个函数到底应该返回什么它应该返回从head开始的这段链表反转之后的新头节点。有了这个语义递归思路就清晰了假设我从head向后走一步对head-next调用reverseList那么返回的是“从head-next开始的链表反转后的新头”。以1 - 2 - 3 - 4 - 5为例如果传入head 1递归会先去处理2 - 3 - 4 - 5返回的新头是5。此时head这个节点值为1还在它的next仍然指向2。而经过递归处理后2的next已经变成NULL因为2成了反转后小链表的尾节点。所以我们要做的事情其实很简单把head接到2的后面。也就是head-next-next head。这句话是递归解法的灵魂。等等为什么head-next-next head而不是某个返回值因为head-next就是节点2在递归处理后它已经变成了反转小链表的尾节点所以2-next head就是把节点1挂到2后面。然后别忘了把head-next置为NULL否则会出现环。最后返回递归结果newHead。struct ListNode* reverseListRecursive(struct ListNode* head) { if (head NULL || head-next NULL) { return head; } struct ListNode *newHead reverseListRecursive(head-next); head-next-next head; head-next NULL; return newHead; }递归的终止条件有两个空链表和单节点链表。注意这里不能只写if (head NULL) return NULL;因为单节点链表在递归过程中其实是最常见的“最后一层”如果没拦住会继续对NULL调用并且head-next本身就是 NULL后续head-next-next head会对空指针解引用直接崩溃。为什么递归法能反转从执行顺序上看它的核心动作发生在“回溯”阶段先一路递到链尾然后在回归过程中逐步把每个节点的 next 反向指回去。每一层只做一件事让自己成为层尾让已经反转好的小链表成为自己的后续部分。这个理解到位了递归代码就是顺理成章的。不过递归解法有一个不能忽视的软肋调用栈深度。链表长度为 n 时递归深度也是 n。如果链表很长比如几万个节点C语言默认栈空间很有限很可能直接栈溢出。面试时如果链表是百万级长度面试官通常希望听到你说“递归有栈溢出风险更稳妥的是迭代法”。2.3 头插法换个角度看反转第三种思路跟迭代法本质上是一回事但换个角度更好理解遍历原链表每次摘下当前节点用“头插法”把它插到一条新链表的最前面。新链表初始为空。遍历结束后新链表就是反转后的链表。实现上用虚拟头节点dummyHead会方便很多。虚拟头节点不存储真实数据只是让我们统一在它的后面插入节点省去每次判断“新链表是否为空”的麻烦。struct ListNode* reverseListByHeadInsert(struct ListNode* head) { struct ListNode dummy; dummy.next NULL; struct ListNode *cur head; while (cur ! NULL) { struct ListNode *next cur-next; // 保存下一个节点 cur-next dummy.next; // 当前节点指向新链表的第一个节点 dummy.next cur; // 当前节点成为新链表的第一个节点 cur next; // 继续处理原链表的下一个节点 } return dummy.next; }你会发现头插法的核心动作和迭代法几乎一一对应区别只是视角迭代法强调“在原链表上原地修改 next 方向”头插法强调“把节点摘下来插到新链表头部”。实际运行效果没有任何区别时间复杂度都是O(n)空间复杂度都是O(1)。看过这三种解法之后做一个对比解法时间复杂度空间复杂度是否容易出错适用场景三指针迭代O(n)O(1)需注意保存next面试首选最通用递归O(n)O(n)调用栈需理解递归语义链表较短时写法简洁头插法O(n)O(1)需要构建虚拟头节点理解链表插入操作很好用三种解法的时间复杂度都是一样的面试时如果你能先写迭代法再补充递归法并且说出两者的空间复杂度差异面试官对你的印象会明显加分——这说明你不是背了一道题而是真的理解了这个操作的底层逻辑。3. C语言实现细节与测试陷阱3.1 结构体定义与入参设计一级指针还是二级指针面试题里给的函数签名通常是struct ListNode* reverseList(struct ListNode* head)这意味着我们接收一级指针head返回新的头节点。但很多人会纠结一个问题参数为什么不设计成二级指针毕竟反转之后原链表头节点变了函数内部如果修改了head的值外部调用者是感知不到的除非通过返回值或者二级指针传出去。实际上面试场景有个潜规则接口签名已经确定了就不要擅自改。面试官给你reverseList(struct ListNode* head)就是想考察你是否能通过返回值来完成“感知新头部”这件事。你当然可以跟面试官讨论“如果要用二级指针也可以但这样调用者需要传head略微麻烦”但你亲手实现的时候还是按已有签名来。等你写出返回值方案面试官心里会暗赞这个人知道C语言函数传参的值传递语义知道指针本身也是值传递。顺便说一句// 单链表节点定义里的结构体名称struct ListNode不是ListNode这在 C 语言里是一个经典的语法点结构体类型必须带struct关键字除非你用typedef做别名。很多面试者因为这个问题卡住一脸茫然地看着编译器报错。建议写题时如果你习惯typedef struct ListNode { ... } ListNode;也完全没问题但面试中为了和题目标题保持一致通常直接用struct ListNode定义并引用。3.2 完整的测试代码怎么写构造、打印、释放题目只要求写反转函数但你真的在本地调试的时候需要自己搭一个完整的测试环境。构造链表、打印链表、反转、打印、释放内存这几步一个都不能少。下面这份代码我建议直接收藏它可以复用到几乎所有单链表题目的调试中。#include stdio.h #include stdlib.h struct ListNode { int val; struct ListNode *next; }; // 创建新节点 struct ListNode* createNode(int val) { struct ListNode *node (struct ListNode*)malloc(sizeof(struct ListNode)); node-val val; node-next NULL; return node; } // 根据数组构造链表 struct ListNode* createList(int arr[], int n) { if (n 0) return NULL; struct ListNode *head createNode(arr[0]); struct ListNode *cur head; for (int i 1; i n; i) { cur-next createNode(arr[i]); cur cur-next; } return head; } // 打印链表 void printList(struct ListNode *head) { struct ListNode *cur head; while (cur ! NULL) { printf(%d, cur-val); if (cur-next ! NULL) { printf( - ); } cur cur-next; } printf(\n); } // 释放链表内存 void freeList(struct ListNode *head) { struct ListNode *cur head; while (cur ! NULL) { struct ListNode *next cur-next; free(cur); cur next; } } // 反转链表迭代版 struct ListNode* reverseList(struct ListNode* head) { struct ListNode *pre NULL; struct ListNode *cur head; while (cur ! NULL) { struct ListNode *next cur-next; cur-next pre; pre cur; cur next; } return pre; } int main() { int arr[] {1, 2, 3, 4, 5}; int n sizeof(arr) / sizeof(arr[0]); struct ListNode *head createList(arr, n); printf(原链表: ); printList(head); struct ListNode *newHead reverseList(head); printf(反转后: ); printList(newHead); freeList(newHead); return 0; }运行输出原链表: 1 - 2 - 3 - 4 - 5 反转后: 5 - 4 - 3 - 2 - 1这段代码里藏着几个测试时容易踩的坑。第一createList函数里如果n 0要返回NULL否则你拿不到空链表来测试边界条件。第二打印链表时要小心最后一个节点如果你在cur-next为 NULL 时还打印 - 输出会多一个箭头所以我加了个判断。第三释放链表内存时不能直接free(cur)然后访问cur-next——因为cur已经释放了再访问cur-next是未定义行为。必须先保存next再free(cur)这就是上面freeList的写法。3.3 打破直观想象的真相节点地址没有变在调试过程中你可能会有个疑问反转链表到底改变了什么答案非常关键一个都没有改变节点的内存地址我们只是把每个节点的next指针从“指向下一个节点”改成了“指向上一个节点”。也就是说原来1号节点的内存地址是0x100反转后它仍然是0x1002号节点地址是0x200反转后它仍然是0x200。变化的是0x100处的next从0x200变成了NULL0x200处的next从0x300变成了0x100以此类推。这一点对调试和理解非常重要。如果你在控制台打印每个节点的地址你会发现反转前后所有节点地址一样只是从头到尾访问的顺序变成了从尾到头。所以反转链表操作可以理解为“重排指针关系”完全不涉及节点内存的复制、移动、释放或重新申请。这也是为什么它的空间复杂度是 O(1) 的原因之一——迭代法没有申请任何额外节点。有些人在学习时总想把链表“整体翻转”或“创建一个新链表”那是一种常见的误解。创建新链表当然可以做但从节点拷贝的角度来看那是 O(n) 空间复杂度面试里通常不是更优解。真正的反转就是改指针。4. 边界条件与面试追问把简单题答出深度4.1 边界测试用例清单我先整理一份边界测试列表你可以对照着用上面的测试代码跑一遍确保万无一失。空链表输入NULL期望返回NULL。迭代法天然满足。单节点链表输入[5]期望返回[5]。迭代法和递归法都能正确返回。两个节点输入[1, 2]期望返回[2, 1]。这个用例能检验next cur-next的备份逻辑是否真的正确。长链表比如 10000 个节点用于检验迭代法的稳定性和递归法的栈溢出风险。实测如果链表长度到 50000 以上递归版在默认栈空间下很容易崩溃迭代法完全没问题。所有节点值相同比如[7, 7, 7]反转后仍然是[7, 7, 7]但你要确认指针关系确实被翻转了而不是因为值相同“看起来没变”。负数和大数链表节点的val可以是任意 int 值写测试时适当覆盖一下防止有人的代码里意外依赖了值的正负性。4.2 面试高频追问一如何用 O(1) 空间解决面试官可能会追问“能不能尽量不使用额外空间”这个问题表面上看是在问空间复杂度实际上是在期待你说出迭代法的 O(1) 空间优势。把链表画一下就能理解我们自始至终只用了三个指针变量pre、cur、next不管链表有多长这三个变量不会多也不会少。这和递归的 O(n) 调用栈形成鲜明对比。这个追问的意图不只是要你背复杂度更重要的是考察你是否知道“指针变量和链表长度无关”这一事实。你可以这样回答迭代版本中输入的链表节点本身是现成的内存我们在原节点上直接修改 next 指向不新建任何节点每次循环只会创建或者复用几个指针变量它们的数量是常量级与链表规模无关因此空间复杂度为 O(1)。4.3 面试高频追问二递归会爆栈吗如果面试官看到你写了递归版本追问“递归有什么问题吗”这时候不要慌张。你要清楚地指出递归调用的深度等于链表长度如果链表很长C语言程序默认的函数调用栈空间有限递归版本可能因为栈溢出而崩溃。在某些在线评测系统里测试链表长度可能只有几百或几千递归也能过但如果是实际产品中几十万节点的链表递归就非常危险了。作为对比你可以顺势说出迭代法的优势“所以工程上我更倾向于用迭代版三指针因为无论链表多长内存占用只与常量级指针相关。”这个回答既体现了你的深度又把话题引导到你熟悉的解法上一箭双雕。4.4 面试高频追问三如果有环怎么办这是稍微进阶一点的追问。标准反转链表的题目默认输入是无环的单向链表。如果面试官问“链表里有环怎么反转”你可以分两步答第一步先明确环的存在会影响反转。在有环的情形下反转逻辑会变成一个无限循环因为cur-next永远不为空。具体表现假如链表存在环cur遍历到环的入口后会一直在环里打转永远不会遇到cur NULL的终止条件。第二步给出检测思路判断链表有没有环可以用快慢指针法慢指针每次走一步快指针每次走两步如果两个指针相遇则说明有环再找出环的入口位置把环断开再反转。这个过程在面试时口头描述清楚即可通常面试官不会再要求你写出带环反转的完整代码。4.5 面试高频追问四能不能在原链表基础上“复制”一份再反转有位读者曾经问我“如果我想反转一个链表但是我不想改变原来的链表怎么办”这其实是追问的变体。思路很直接先把原链表完整地复制一份用复制后的新链表做反转。此时原链表不受任何影响你得到的是一个独立的反转后链表。代价是时间和空间复杂度都变为 O(n)。这个方案在题目标准接口下通常不会用到因为标准接口明确反转的是原链表但理解这个方案能帮助你把“节点复制”和“指针指向调整”两件事彻底分开。struct ListNode* copyAndReverseList(struct ListNode* head) { // 先拷贝每个节点 struct ListNode dummy; dummy.next NULL; struct ListNode *cur head; while (cur ! NULL) { struct ListNode *newNode createNode(cur-val); newNode-next dummy.next; dummy.next newNode; cur cur-next; } return dummy.next; }注意上面的代码在拷贝的过程中其实已经顺手完成了反转——因为你每次都把新节点插到结果链表的头部。如果你想要“拷贝但不反转”那就按tail-next newNode的方式追加到尾部如果你想要“拷贝且反转”头插法天然做到了。这个变量的对比是理解链表操作的一个很好的练习。5. 进阶扩展从反转整个链表到反转局部区间5.1 反转前 N 个节点如果面试题继续追加难度常见的进阶方向是“只反转链表的前 N 个节点”。比如1 - 2 - 3 - 4 - 5反转前 3 个节点后变成3 - 2 - 1 - 4 - 5。这个操作需要额外记录两个变量反转区间的前驱节点和后继节点。核心思路先用一个计数器走 N 步记录走到第 N 个节点时的位置此时要从1这个位置开始做局部反转反转的长度是 N。反转完成后原本在区间后面的部分要重新连接回来。struct ListNode* reverseFirstN(struct ListNode* head, int n) { if (head NULL || n 1) return head; struct ListNode *pre NULL; struct ListNode *cur head; struct ListNode *next NULL; for (int i 0; i n cur ! NULL; i) { next cur-next; cur-next pre; pre cur; cur next; } // 此时 head 是原区间的第一个节点反转后变成了区间的最后一个节点 // 它的 next 应该指向区间的后继节点也就是 cur head-next cur; return pre; }注意最后一步head-next cur。这一步很关键因为迭代法在反转前 N 个节点时循环结束时cur正好停留在区间的后继节点可能是 NULL也可能是第 N1 个节点。把原头节点的 next 挂到cur上区间反转后的尾部才能和后半部分衔接上。5.2 反转区间 [m, n] 的节点再进一步就是“反转区间 [m, n] 的节点”这是反转前 N 个节点的推广。比如1 - 2 - 3 - 4 - 5反转区间 [2, 4] 后变为1 - 4 - 3 - 2 - 5。这个操作在力扣第 92 题里出现但剑指 Offer 24 的代码基础完全可以套用。实现思路是这样的第一步找到第 m-1 个节点记作prev它是反转区间的前驱节点第二步从第 m 个节点开始反转共反转n - m 1个节点第三步把反转后的区间的头部和尾部分别接到prev和原区间后继节点上。注意m 1时没有前驱节点返回的是反转后的新头节点。struct ListNode* reverseBetween(struct ListNode* head, int m, int n) { if (head NULL || m n) return head; struct ListNode dummy; dummy.next head; struct ListNode *prev dummy; // 找到第 m-1 个节点 for (int i 1; i m; i) { prev prev-next; } struct ListNode *start prev-next; // 反转区间的第一个节点 struct ListNode *cur start; struct ListNode *pre NULL; // 反转 n-m1 个节点 for (int i 0; i n - m 1; i) { struct ListNode *next cur-next; cur-next pre; pre cur; cur next; } // 此时 pre 是区间反转后的新头cur 是区间的后继节点 start-next cur; prev-next pre; return dummy.next; }这里的虚拟头节点dummy用得恰到好处它让m 1这一边界情况变得不需要额外判断。如果没有dummym 1时prev根本不存在你需要单独写一个分支。用虚拟头节点prev永远可以指向一个存在的节点整个逻辑统一了。5.3 K 个一组反转链表再往上走还有“每 K 个节点一组反转”这个经典题目。它的基本思路是先看剩余节点数是否够 K 个不够的直接返回够了就反转这一组 K 个节点然后把反转好的组与前面的组连接起来继续处理下一组。这个题目同样可以复用我们上面三指针反转的核心代码只是在每一组的开头结尾多了一些判断和连接。面试时你如果能从“反转整个链表”一路写到“K 个一组反转”那基本确立了你在链表题目上的碾压优势。我之前认识一个同学面试时面试官问“你会反转链表吗”他写了迭代版面试官又问“那反转前 3 个节点呢”他改改代码也写出来了再问“那每 3 个一组反转整个链表呢”他虽然没完全写出来但把思路讲明白了面试官最后还是给了通过的反馈。这个故事的启示是链表反转绝对不是孤立的单点技能而是很多高难度链表题的地基。地基打得越牢扩展越轻松。6. 实操踩过的坑手写代码时最容易被忽略的细节6.1 返回值错误忘记把 pre 返回出来迭代法写到最后应该返回pre而不是head或cur。我见过太多次这种错误代码逻辑完全正确循环也跑完了最后return head;结果反转后的链表变成了从原头节点开始的一段断链。为什么会有人犯这种错因为刚开始学的时候脑子里还想着“反转之后原来的头还在那条链上嘛”其实原来的头已经成为链表的尾节点它的 next 已经变成 NULL你返回它只能拿到一个只含一个节点的链表。建议每次写完反转代码后都问自己一句此时哪个指针指向反转后链表的头节点迭代法循环结束后答案是pre递归法首次调用的返回值就是新头但在递归内部别把head返回了。6.2 递归函数里丢掉 newHead递归写法里有一个隐蔽的坑递归调用的返回值newHead必须在每一层都被传回去。如果你在递归回溯过程中重新选择了返回head那第一层拿到的新头就是原头节点反转结果不完整。// 错误写法示例示意不要这样写 struct ListNode* wrongReverse(struct ListNode* head) { if (head NULL || head-next NULL) return head; struct ListNode *newHead wrongReverse(head-next); head-next-next head; head-next NULL; return head; // 错误这里必须返回 newHead }这个错误在代码量小、逻辑看似“正确”的情况下很难一眼看出来。用测试[1, 2, 3]去跑错误版本返回的是1打印出来只有1你才知道丢掉了后面的节点。调试链表题的时候最好把“打印整个链表”作为基本步骤融进肌肉记忆。6.3 未定义行为访问已释放节点在一些变体题目中需要“删除某个节点后返回头节点”如果释放节点时顺序不对比如先free(cur)再访问cur-next代码在大多数机器上看起来还能工作实际上是典型的未定义行为。链表相关的调试问题大多数都发生在这一块指针悬空、内存泄漏、二次释放。反转链表本身不涉及释放任何节点但如果你把反转代码和其他链表操作叠加就要格外小心内存生命周期的管理。我个人建议在面试场景中写链表题时如果题目没有要求释放节点就完全不提到释放只说“我只修改指针关系不涉及内存分配和释放”——这句话能让面试官放心也能帮助你集中注意力在核心逻辑上。6.4 变量名与代码风格面试官会看你的“手感”写链表题时变量名不是随便起的。pre、cur、next这种命名非常直观一看就懂。有的人写反转链表用a、b、c或者p、q、r虽然也能运行但面试官在阅读代码的时候需要额外的心智负担去猜每个字母代表什么。作为候选人你应该尽量让面试官在 30 秒内读明白你的代码。此外循环的边界也很见功力。用while (cur ! NULL)的语义是“当前节点还有效就继续反转”用for (int i 0; i n; i)的语义是“反转指定长度的节点”。面试现场手写代码时如果你不清楚当前走的是“遍历到末尾”还是“反转固定长度”循环条件写错了整个程序都会乱。这一点没有捷径只能靠多写、多走测试用例来强化。6.5 在线评测的时候会不会超时通常情况下反转链表不可能超时O(n) 的时间复杂度已经是最优了。但如果你的代码里不小心在循环内部调用了遍历函数或者每次递归都从头访问链表那复杂度可能变成 O(n^2)。说白了反转链表这道题的天花板很低如果你在规定时间内没写出来核心原因大概率不是题目难而是对指针操作不熟练。想检验自己的熟练度你可以给自己一个目标白纸上手写三指针迭代法控制在 2 分钟内一次编译通过且无语法错误。能稳定做到这一点才算真正过了链表基本功这一关。7. 总结之外的实话一道简单题带来的长期收益刷题刷到“反转链表”这种题时很多人会嫌弃它太简单跳过去继续刷难题。但我的经验恰恰相反——这道题值得反复练练到形成肌肉记忆。为什么因为链表题几乎所有复杂操作都建立在两个基本功上遍历链表和反转局部链表。反转不仅是题目本身更是一种“操作模式”把一段链表的 next 全部反向然后把两端的头尾接到该接的地方。理解了这种模式后面处理区间反转、K 个一组反转、回文链表判断快慢指针找中点 后半段反转、两两交换节点等题目都会轻松很多。我在实际练习中有个习惯同一个题目用三种解法都写一遍再故意写几个错误版本观察错误的后果。比如把next cur-next删除后再跑看链表是不是从第二个节点起直接丢失把递归返回head而不是newHead再跑观察输出只保留了第一个节点。这种“故意犯错”的练习方式比反复做对题更能加深理解因为你会亲眼看到指针断连的后果而不是只在理论层面知道“可能会断”。如果你正在准备面试我的建议是把这道题当作热身题在刷每一轮题之前先花五分钟写一遍三种解法。不要觉得重复无聊等到面试现场你才发现这些“无聊”的动作已经内化成了一种本能。写链表题最怕的不是不会而是在压力下突然想不起某一步为什么要先保存 next。而大量的重复练习正是消除这种紧张感的唯一途径。反转链表只是起点后面的路还很长。希望你从这道题里收获的不只是会写一个函数而是建立起对链表操作的信心——这份信心会在你面对更复杂的数据结构题目时成为你最坚实的后盾。