两数相加链表题全解析:从哨兵节点到进位处理的面试指南 1. 题目拆解与核心思路这道题到底在考什么“两数相加”在 LeetCode 热题 100 里属于链表章节的入门题但如果你只把它当成“模拟加法”来做面试会吃亏。先看题面给你两个非空的链表每个节点存一个数字数字按逆序存储也就是个位在链表头。要求你返回一个新的链表表示两个数相加的结果同样按逆序存储。举个具体例子输入l1 [2, 4, 3] 代表数字 342 l2 [5, 6, 4] 代表数字 465 输出[7, 0, 8] 代表数字 807为什么是逆序存储这恰恰是这道题最“友好”的地方从链表头部开始遍历正好就是从个位开始加符合我们手算加法的习惯不需要做任何反转操作。你从两个链表的头节点出发逐位相加向后进位遍历完两个链表之后剩下的进位再补一个节点就结束了。这道题表面上是链表操作本质上考的是三件事能否熟练使用哨兵节点dummy head简化链表头部的处理逻辑能否正确维护进位状态尤其是遍历完所有节点后还残留进位的情况能否稳定地处理两个链表长度不一致的边界条件这三件事任何一件没处理好代码就会出现隐蔽的 bug。比如最常见的错误循环条件写成while (l1 ! null l2 ! null)两个链表一样长的时候没问题一旦一个链表多出几位就直接丢数据。这种错误在真实面试中出现的频率非常高。适合谁来读这篇如果你正在准备大厂算法面试或者刷 LeetCode 过程中发现自己链表基础不牢又或者你只是想把这道题吃透、理解它背后的“大数加法”思想这篇都值得你看完。我会从最基础的实现开始逐步拆解到面试追问、变体拓展和工程化优化把一道题的完整知识图谱铺开。2. 基础版实现详解从伪代码到逐个节点操作2.1 归零心态先把链表结构定义说清楚LeetCode 内置的链表节点定义是这样的Java 版public class ListNode { int val; ListNode next; ListNode() {} ListNode(int val) { this.val val; } ListNode(int val, ListNode next) { this.val val; this.next next; } }每个节点只保存一个 0~9 的数字加上指向下一个节点的引用。所有链表题的操作本质上都是围绕val的读取和next的重新指向展开的。这道题里我们是“新建一条链表”来存结果而不是在原地修改 l1 或 l2这样思路最清晰也符合大多数面试官对第一版代码的预期。新建链表时最常用的手段就是哨兵节点ListNode dummy new ListNode(0); ListNode tail dummy;为什么要造一个不存实际数据的哑节点因为在构造链表时头节点往往是最后才知道具体值的第一个节点的值可能是0也可能不是如果不用哨兵你就得单独处理“当前结果链表为空”的判断代码会多出很多分支。用了哨兵之后所有新增节点都统一挂在tail.next上最后返回dummy.next干净利落。2.2 核心循环一次遍历完成逐位相加先给出最直接的实现再逐行拆解class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { ListNode dummy new ListNode(0); ListNode tail dummy; int carry 0; while (l1 ! null || l2 ! null || carry ! 0) { int sum carry; if (l1 ! null) { sum l1.val; l1 l1.next; } if (l2 ! null) { sum l2.val; l2 l2.next; } tail.next new ListNode(sum % 10); tail tail.next; carry sum / 10; } return dummy.next; } }这段代码的核心逻辑我拆成四步来看第一步循环条件写成l1 ! null || l2 ! null || carry ! 0而不是l1 ! null l2 ! null。这是整道题最重要的一个设计决策。它保证了三种情况都能被处理两个链表都不为空时正常相加一个链表为空时继续处理非空链表的剩余节点以及最后全部遍历完后如果还有进位比如 5 5 10会再生成一个新节点来存放这个 1。第二步进入循环后先初始化sum carry把上一次的进位带进来。然后分别判断 l1 和 l2 是否为空为空就当作 0 处理。这种写法避免了在循环外单独处理“某个链表遍历完了”的情况也避免了对空指针调用.val的 NPE 风险——因为你每次都先判空再取节点并移动指针。第三步sum % 10是当前位的结果sum / 10是新的进位。因为每个节点只存 0~9所以结果一定在 0~9 之间而两个一位数加上一个进位的最大和是 9 9 1 19所以进位只可能是 0 或 1不会出现进位等于 2 的情况除非输入本身不合法。第四步把新节点挂到tail.next上然后tail tail.next。注意这里没有单独维护一个“当前节点的索引”而是用 tail 指针一直指向结果链表的最后一个节点。每创建一个新节点tail 就后移一位。2.3 为什么一定要用哨兵节点从痛苦经历说起我第一次不写哨兵节点的时候代码长这样ListNode head null; ListNode tail null; while (...) { if (head null) { head new ListNode(sum % 10); tail head; } else { tail.next new ListNode(sum % 10); tail tail.next; } } return head;能跑但每加一位都要判断一次 head 是否为 null。代码长了冗余了而且容易在循环内部引入拼写错误。面试时手写代码这种不必要的分支判断会让你花更多时间在“保证逻辑正确”上而不是把精力留给更核心的进位处理。哨兵节点的本质是预埋一个虚拟的前驱节点让所有真实节点的处理方式完全一致。这是链表操作里最经典的统一化技巧不仅在“两数相加”里用在链表删除、合并、反转等一系列题目中都能用。面试时你写出哨兵节点本身就是加分项因为这表明你不是第一次写这种代码而是有自己沉淀的工具箱。3. 面试高频考点这是面试官最想测试的五个细节3.1 考点一循环不变量——你能否说清楚每一行代码的意义面试官大概率不会只满足于“代码能跑”。他会问你为什么循环结束之后不需要再判断一次carry 0答案是因为循环条件已经包含了carry ! 0。这是循环不变量的典型体现每次进入循环时需要处理的状态可以用“l1 剩余部分、l2 剩余部分、进位”这三个变量完整描述而循环体做的事情就是把这些状态归约到更小的规模。当三者都归零时计算自然结束。我在模拟面试中遇到过不少候选人代码能写出来但问他“为什么循环条件是三者的或关系”时答不上来。这说明他只是背下了模板没有真正理解状态转移。面试官一问就露馅。3.2 考点二大数加法的本质——为什么链表适合处理超大整数普通整形数相加的容量是有限的。Java 的int最大是2147483647约 21 亿long最大也才9223372036854775807约 922 亿亿。但链表版本的“两数相加”理论上可以处理任意位数的整数——因为每个节点只保存一位链表可以无限长。这就是“链表模拟大数加法”的底层逻辑用线性结构打破基本数据类型的位数限制。类似的思路在字符串相加415 题、二进制求和67 题、大数乘法43 题里都有体现。你在这道题里掌握的“逐位相加、维护进位、反向遍历”思想可以无缝迁移到那些题目中。面试官如果追一句“如果这两个链表表示的数是 10 的 100 次方量级你的代码还成立吗”正确答案是成立因为代码里从不把整个数字读入内存而是按节点逐位处理时间复杂度只与较长的链表长度成正比与数字本身的大小无关。3.3 考点三边界条件检查清单这道题常见的边界条件我总结了一个清单面试前建议反复过一遍场景输入示例关键点两个链表长度相同无进位12 34最常规场景考验基本循环两个链表长度相同有进位5 5多出一个头节点新链表长度比两者都长长度不同无进位1 999需要在短链表遍历完后继续处理长链表长度不同有进位1 999长链表遍历完后还有残留进位其中一条链表为空null 或 []题目说输入非空但面试官会问如果为空怎么处理进位不断传递999 1结果变成 1000链表长度比输入多一位最后一种情况最容易被忽视999 1的结果是1000需要从个位开始逐级进位最终在最高位新增一个 1。很多新手在循环里只处理了sum / 10的进位却没有在循环条件里加入carry ! 0导致结果变成[0, 0, 0]直接漏掉了最高位的 1。这种错误在 LeetCode 里会显示为“Wrong Answer”在面试中则会被直接指出。3.4 考点四能否用递归实现递归和迭代的取舍这道题也可以用递归写。基本思路是“当前位的和加上后续递归处理的结果”class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { return addRecursive(l1, l2, 0); } private ListNode addRecursive(ListNode l1, ListNode l2, int carry) { if (l1 null l2 null carry 0) { return null; } int sum carry; if (l1 ! null) sum l1.val; if (l2 ! null) sum l2.val; ListNode node new ListNode(sum % 10); ListNode nextNode addRecursive( l1 null ? null : l1.next, l2 null ? null : l2.next, sum / 10 ); node.next nextNode; return node; } }递归版的优点在于代码更短逻辑更接近“函数的定义自己”。但缺点也很明显如果链表长度非常长比如百万级别递归会导致调用栈溢出StackOverflowError而迭代版本则没有这种问题。所以我的建议是面试时首选迭代实现理由有三点一是稳定性更好不受递归深度限制二是迭代的指针移动更直观更容易向面试官解释三是后续如果面试官追问优化迭代版本更容易在此基础上做空间上的改进。但递归版本值得在脑子里有印象因为面试官偶尔会问“你能不能换个写法”你如果能现场改出递归版会是非常好的加分表现。3.5 考点五能否原地修改 l1减少空间占用如果面试官进一步追问“我可以直接复用 l1 的节点来存结果吗”答案是可以但需要注意陷阱。原地修改的核心思路是把相加的结果写回 l1 的节点上只有当 l2 比 l1 长时才需要继续遍历 l2 并把多余节点“接”到结果链表的末尾。但陷阱在于如果你直接把某个值写回 l1 节点而这个节点恰好又被其他地方引用比如面试官给你构造的测试用例中l1 后续仍需要被访问就会出现副作用。LeetCode 的判题系统通常不检查输入是否被修改但真实项目里这种副作用往往会导致不可预期的 bug。更关键的是原地修改并没有本质上的性能提升——因为无论如何你都需要遍历完两个链表新建节点的开销在面试场景下完全可以忽略。我给你的建议是第一版代码用“新建节点”面试官如果问能不能省空间你再解释原地修改的思路和潜在风险。这样既展示了你对空间复杂度的敏感又体现了一种“在效率与安全之间权衡”的工程判断力。4. 从基础到进阶常见变体与扩展思路4.1 变体一字符串相加LeetCode 415这道题与“两数相加”几乎同构只是把链表换成了字符串。思路完全一样从两个字符串的末尾个位开始逐位相加维护进位把结果存到 StringBuilder 里最后反转输出。关键差异有两点一是字符串可能长度相差很大短的字符串在下标越界时要按 0 处理二是字符串的“头部”是最高位而“两数相加”的链表头部是个位所以遍历方向相反。理解了“两种线性结构上的双向遍历”之后这两道题其实可以当成同一道题来做。4.2 变体二链表正向相加LeetCode 445两数相加 II题面改成了链表按正向存储数字比如[3, 4, 2]代表 342。这时候就不能直接从头遍历了因为两个链表的头分别代表最高位而不能先加最高位。最经典的做法是先把两个链表反转然后用我们熟悉的“逆序相加”逻辑得到结果后再反转回来。或者更优雅一点用两个栈先把所有节点压入栈中弹出时自然就是逆序ArrayDequeInteger stack1 new ArrayDeque(); ArrayDequeInteger stack2 new ArrayDeque(); // 遍历 l1 和 l2分别压入 stack1 和 stack2 // 然后依次弹出栈顶元素相加结果用头插法构造用栈的好处是代码清晰不需要写反转链表的辅助函数。坏处是额外的 O(n) 空间。而“反转 逆序相加 再反转”的空间复杂度可以做到 O(1)除了结果链表但代码更长。面试时优先选栈方案思路好讲不烧脑。4.3 变体三二进制链表求和LeetCode 67如果把十进制数字换成二进制逻辑几乎不变唯一需要调整的是sum % 2和sum / 2。因为二进制的进位只有 0 和 1 两种每位的和最小是 0最大是 31 1 1用了sum / 2之后进位也只会是 0 或 1。这其实是在提醒你这道题的核心并不是“十进制”而是“按位加法 进位传递”这个通用的状态机思维。换成八进制、十六进制甚至自定义进制公式都写成sum % base和sum / base即可。4.4 变体四负数怎么处理面试官偶尔会脑洞大开问“如果链表表示的是负数怎么办”。两个方向一是按绝对值相加然后根据符号位确定结果符号——前提是你能明确定义负数的链表存储方式比如第一位存符号二是在加法器中引入补码表示用定长链表存储二进制补码把减法问题转换成加法问题。第二种思路更接近真实计算机的硬件实现但这已经超出了常规面试题的考察范围。遇到这种问题时先问清面试官的意图再给出合理假设和方案。不要一根筋地往一个方向死磕沟通本身就是面试评分的一部分。5. 实战优化代码冗余判断消除与可读性提升5.1 优化点一减少循环内部的计算在最基础的版本中每次循环都会执行sum % 10和sum / 10两个操作这没问题因为循环次数最多是max(m, n) 1而这个操作是 O(1) 的。但严格来说sum % 10和sum / 10在 Java 里是两次除法运算属于相对昂贵的操作。如果链表长度达到百万级这种计算量就会变得可观。当然这道题在 LeetCode 上根本不会遇到百万级长度的输入优化价值有限。但在真实业务中如果你用同样的模式处理一个超大整数流可以考虑用位运算优化比如十进制下sum % 10可以用查表法或者直接让 sum 保存为短整型以减少内存占用。不过这些优化属于“用代码复杂度换细微性能”的行为面试中不建议主动做——除非面试官明确问你怎么优化。5.2 优化点二数组预填充结果如果只是追求 LeetCode 上的极致性能一个技巧是先遍历两个链表得到长度然后分配一个max(m, n) 1长度的数组把结果写入数组对应下标最后再根据数组构造链表int len1 getLength(l1); int len2 getLength(l2); int maxLen Math.max(len1, len2) 1; int[] res new int[maxLen];这样做的好处是省去了大量节点创建的开销坏处是空间复杂度从 O(1) 变成 O(n)而且代码更长。在面试中我更推荐直接操作链表因为题目考的是链表操作能力而不是数组技巧但在实际工程中如果数字以连续内存形式存在数组方式往往更快。5.3 优化点三关注内存与可读性之间的平衡很多初学者会把代码写得特别“紧凑”比如while (l1 ! null || l2 ! null || carry ! 0) { int sum (l1 ! null ? l1.val : 0) (l2 ! null ? l2.val : 0) carry; tail.next new ListNode(sum % 10); tail tail.next; carry sum / 10; if (l1 ! null) l1 l1.next; if (l2 ! null) l2 l2.next; }这段代码比之前的版本省了几行但可读性下降了——因为你在一个表达式里同时做了“取 val”“判空”两件事。面试时为了让面试官能快速跟上思路我更倾向于先判空再相加的写法清晰第一性能第二。5.4 一个更注重可读性的参考实现综合以上考虑我在面试中更偏爱下面这个版本它把每一步的意图都摊开了class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { ListNode dummy new ListNode(0); ListNode tail dummy; // tail 总是指向结果链表的末尾 int carry 0; while (l1 ! null || l2 ! null || carry ! 0) { int sum carry; if (l1 ! null) { sum l1.val; l1 l1.next; } if (l2 ! null) { sum l2.val; l2 l2.next; } tail.next new ListNode(sum % 10); tail tail.next; carry sum / 10; } return dummy.next; } }这段代码适合面试也适合作为以后写链表题的“脚手架”哨兵节点、tail 指针、进位维护三件套齐全。6. 复杂度分析与测试用例设计为什么这是 O(max(m, n))6.1 时间复杂度与较长链表长度线性相关设 l1 的长度为 ml2 的长度为 n。我们的循环每执行一次至少消耗 l1 中的一个节点或 l2 中的一个节点实际可能是两者同时消耗。循环终止的条件是 l1 和 l2 都为空且 carry 为 0所以循环最多执行max(m, n) 1次——为什么加 1因为最后一次可能只是处理残留的进位。所以时间复杂度是 O(max(m, n))线性级别。6.2 空间复杂度准确说是 O(max(m, n))但可以解释为 O(1)新创建的链表就是结果本身它的长度最多是max(m, n) 1所以如果不把结果链表算进额外空间额外空间只有dummy、tail、carry几个变量是 O(1)。如果面试官问的是总空间包括输出那就是 O(max(m, n))。怎么回答更稳妥你可以说“我使用了额外常量级的辅助空间但结果链表本身占用 O(max(m, n)) 空间。如果题目要求我们返回结果这通常是必然的开销不算额外空间。”6.3 测试用例设计不只是跑两个用例我习惯在面试中当场写出几个测试用例给面试官看而不是等面试官给用例这是一种主动的表现用例1l1 [2, 4, 3], l2 [5, 6, 4] 预期[7, 0, 8] 用例2l1 [0], l2 [0] 预期[0] 注意结果链表只有一个节点dummy.next 指向这个节点 用例3l1 [9, 9, 9, 9, 9, 9, 9], l2 [9, 9, 9, 9] 预期[8, 9, 9, 9, 0, 0, 0, 1] 注意这里 l1 长度大于 l2过程中需要持续进位 用例4l1 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] l2 [5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 预期[6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]当然面试中不需要列 30 位的用例但至少要把“短链表结束”“进位残留”这两个场景覆盖到。6.4 调试技巧打印链表而不是打印 node.val我踩过一次坑调试时只打印l1.val结果发现输出永远只有头节点——因为我忘了在循环里更新 l1 指针。后来我养成习惯写一个链表转字符串的辅助方法把整条链表打印出来private String listToString(ListNode head) { StringBuilder sb new StringBuilder(); while (head ! null) { sb.append(head.val).append( - ); head head.next; } sb.append(null); return sb.toString(); }调试链表题时把“当前状态”完整打出来永远比只打印某个 val 高效得多。7. 常见问题与排查经验之谈全是踩过的坑7.1 问题一结果缺少最高位的进位症状5 5输入输出是[0]预期应该是[1, 0]。原因循环条件只写了while (l1 ! null || l2 ! null)没有包含carry ! 0导致最后一次进位没有节点可挂。排查手动模拟l1 [5], l2 [5]你会发现循环内算得 sum 10carry 1tail.next 指向 val0 的节点但循环体结束后不会再有新节点生成。修复循环条件加上|| carry ! 0。7.2 问题二空指针异常症状执行时抛出NullPointerException。原因在循环里直接写了int sum l1.val l2.val carry没判断 l1 或 l2 是否已经为 null。如果较短的链表先遍历完再访问l1.next或l1.val就会 NPE。修复要么在循环体内判空要么在进入循环前把“已到尾部的链表”用一个值为 0 的哨兵节点替换但这样要多构造一个 dummy 0 节点并不优雅。推荐判空。7.3 问题三循环后忘记更新指针导致死循环症状程序运行超时。原因在循环体内创建了新节点tail 也移动了但 l1 和 l2 的指针没有移动导致每轮循环处理的是同一个节点。排查在循环体内检查每一行看 l1 和 l2 的移动语句是否被执行到了。如果你把移动语句放在判空之前可能会出现“l1 已为 null 但还访问 l1.next”的情况或者干脆没写移动语句。7.4 问题四把进位传播写成sum carry; sum ...时漏掉了对 carry 的更新症状[2,4,3] [5,6,4]期望[7,0,8]输出却是[7,0,7]。原因计算出sum 10后应该carry sum / 10 1但代码里写成carry 1写死了。然后下一位sum 4 6 carry 10 1 11结果变成 1进位又变成 1最终最高位漏掉。修复进位永远用sum / 10计算不要写死。7.5 常见问题速查表症状可能原因修复方向输出缺少最高位进位循环条件没包含 carrywhile 条件加|| carry ! 0空指针异常没判空就访问 val先判空或把 null 视为 0超时/死循环忘记移动 l1/l2 指针在循环体末尾统一移动指针输出结果错位进位计算错误进位统一为 sum / 10返回了哨兵节点本身返回了 dummy 而不是 dummy.next返回 dummy.next7.6 一个隐藏的面试陷阱输入链表能不能为 null题目描述里说“非空链表”但面试官可能会改条件“如果其中一个链表是 null你的代码怎么办”我的答案是在循环里判空天然支持 null 输入——因为l1 null时按 0 处理。所以上述实现已经对 null 输入做了防御。如果某个版本的代码没有判空你要能够现场说出来这种情况会 NPE。这种“题目没说但现实可能出现”的边界条件最能体现工程师的防御性编程意识。8. 大整数加法思路从算法题到真实业务场景8.1 为什么我们需要链表模拟大数加法真实业务中“大数”场景非常常见金融系统里金额计算要求极高精度超出double的安全范围科学计算中质因数分解、圆周率计算需要千位甚至万位的整数精度密码学中RSA 算法的密钥通常有 1024 位到 4096 位远超long类型的上限Java 里有一个BigInteger类专门解决这类问题。但它的底层实现本质上就是数组或链表逐位存储数字并且用类似的“逐位相加”算法来执行加法。8.2 从这道题抽象出的可复用模式“两数相加”不是一道孤立的题目它是一个基础模式模式一双流同步遍历—— 两个输入结构并行前进谁短谁先结束剩余部分继续处理模式二状态变量维护—— 在循环体外部维护一个状态如进位每轮循环读取旧值、更新新值模式三哨兵节点避免特殊分支—— 让所有节点处理逻辑保持一致这三个模式是链表题的地基。你刷了几十道链表题之后回头看会发现它们全都在反复使用这三板斧。这也是为什么很多经验帖把“两数相加”列为链表题的“必修一”。8.3 从算法题到工程实践的思维转换如果在真实项目里我们通常不会手写一个大数加法类而是直接用BigDecimal或BigInteger。但算法题的价值在于让你理解这些类背后的原理而不是让你在业务代码里重复造轮子。更实际的应用场景是面试中的代码设计题让你设计一个简历解析模块、一个订单金额汇总器里面涉及超大数字的累加或者让你处理一个 CSV 文件里存了超过long范围的 ID。这时候你学过“两数相加”就自然知道怎么按位处理、怎么避免精度丢失、怎么做进位传递。9. 敲定代码前的几个小习惯适合所有链表题9.1 先写注释再写代码面试时我会在代码前先写三行注释把思路固定住// 1. 双指针从两个链表头开始同步遍历 // 2. 维护 carry 进位初始值为 0 // 3. 循环条件要包含 carry ! 0防止漏掉最高位进位写注释的过程就是在脑子里把逻辑跑一遍。很多 bug 在写注释阶段就能被提前发现。9.2 用变量名表达意图l1、l2是 LeetCode 给定的参数名但循环内部尽量用p、q这样简短的变量名或者直接用curL1、curL2。tail 指针一定要叫tail或cur别叫node——因为node太模糊你不知道它指的是哪个节点。9.3 边界条件测试要形成肌肉记忆链表题的边界条件无非是四种链表为空链表只有一个节点操作发生在头节点操作发生在尾节点之后“两数相加”题涉及的是前两种和后一种输入链表可能只有一位结果可能比输入多一位。每次写完链表题都强制自己把这四种边界过一遍你就不会再犯低级错误。9.4 善用null语义在“两数相加”中把 null 视为 0是一个非常自然且优雅的设计。这个设计不仅简化了代码也让逻辑更接近数学加法定义。类似的思路在设计模式中叫“空对象模式”Null Object Pattern—— 用空对象代替 null 判断避免散落各处的条件分支。你可以在面试里顺嘴提一句这个概念面试官会很受用。10. 针对初学者的五个常见误区与进阶建议10.1 误区一把链表节点当成数组元素直接随机访问链表不像数组不支持按索引 O(1) 取值。你必须从头节点开始通过next跳转。在“两数相加”里这个特性反而带来了便利因为你本来就要从个位开始按顺序处理。10.2 误区二以为结果链表的节点值可以是两位数每个节点只允许存 0~9。如果你把 sum 直接赋给new ListNode(sum)而不是new ListNode(sum % 10)输出就会包含两位数甚至三位数的节点直接违反题意。10.3 误区三只会在纸上模拟不敢直接写代码很多初学者习惯先在纸上画半天确定每一步的值再动手。这很安全但面试时时间有限。我建议你直接写一个“骨架版本”先把循环搭出来再把细节填进去——哪怕第一版代码有错也比一张白纸强。面试官更看重你调试和修正的过程。10.4 误区四认为空间换了时间就是最优解“两数相加”这道题用 O(n) 的辅助空间比如把链表转成数组再相加确实更容易理解但这不是最优算法。追求 O(1) 额外空间的原地处理才是这道题的价值所在。当然如果你能用数组方案先给出正确代码再逐步优化到链表直接操作这种递进式展示在面试中非常讨喜。10.5 误区五刷题时只看题解不写代码这是最常见的学习误区。看题解时觉得“哦原来这么简单”关闭页面后自己写却卡壳。正确的刷题流程是先自己思考 15 分钟再写代码再对照题解找差异。每道题至少独立写一遍代码最好隔几天再重写一遍。11. 最后再分享一个小技巧徒步与链表之间没有本质区别很多人觉得链表很难理解是因为总是用一个抽象的“节点”概念去理解。我建议把链表想象成一条路上前后相连的站点你从起点出发每到一个站点只能看到当前站点的名字val和指向下一站的路牌next。你不能跳着走也不能回头。理解了“单向通行”这个限制指针移动的每一步都是理所当然的。“两数相加”只要顺着这条路走处理了长度差和进位就通关了。刷题时多想想这个类比很多链表题都会变得直观起来。我个人在反复讲这道题的过程中最大的体会是把一道基础题讲透比粗刷十道题更有价值。每次回看这道题我都会发现新的理解角度——比如从状态机的角度、从空间优化的角度、从递归与迭代对比的角度——每一个角度都在加深对链表的掌控感。如果你在刷题路上也遇到了反复出错的链表题不妨用同样的方法多问几个为什么多写几个测试用例多讲给身边的朋友听。能把它讲明白才是真的会了。