☰
链表相加与大数相加:竖式原理、进位处理与高精度实现
2026/11/3 4:52:34 网站建设 项目流程

做后端的朋友大概都碰到过这种场面:账务系统里两个流水号要拼成一个对账键,位数二十多位,long long塞进去直接溢出成负数;或者前端传上来一串用户填的金额,长度不限,后端一转换就精度丢失。这类问题最后往往会绕回一个特别朴素的数据结构——链表相加,也就是常说的大数相加。它的思路一点不玄:把每一位数字拆开挂在链表节点上,然后模拟小学竖式,从最低位开始一位一位往上加,进位用一个变量拎着。听起来简单到不像个正经算法,但真正写起来,边界条件和指针操作能把人坑得怀疑人生。

这篇内容想聊的就是这个东西。它适合三类人:正在啃链表基础、想找个有实际意义的练手题的初学者;面试前想把这道高频题彻底吃透、连变形一起打包的求职者;以及手里真有高精度计算需求、需要一套能直接抄走的实现方案的工程同学。我不会只给你一段能过的代码就完事,重点是讲清楚每一步为什么这么设计、哪种写法会在什么场景下翻车、以及从加法能延伸出多少种变体。

1. 先把问题看清楚:链表相加到底在解决什么

1.1 整型放不下的那道墙在哪里

先算一笔账。C/C++/Java 里的 64 位有符号整数,能表示的最大值是 9223372036854775807,十九位十进制数。也就是说,两个十八位数相乘,结果立刻溢出;两个十九位数相加,也可能溢出。而现实里的数字经常超线:银行流水号、加密算法里的模幂中间值、科学计算里的阶乘、甚至某些业务系统里拼出来的雪花 ID 加偏移量,都可能轻松突破二十位。

这时候只有两条路。一条是用语言自带的任意精度类型,比如 Python 的int天生就是大整数,Java 有BigInteger,C# 有System.Numerics.BigInteger。另一条就是自己造轮子:用某种数据结构把数字逐位存起来,再自己实现加减乘除。链表相加这道题,本质上就是第二条路里最基础的那一步——加法。

我一直觉得这道题的价值不在算法本身有多难,而在于它是一个非常好的“指针操作 + 边界思维”训练场。你得同时推进两条链表、维护一个进位、处理长度不等、处理最后的残留进位,任何一个环节漏掉,结果就是错的,而且错得很隐蔽。

1.2 竖式加法搬到机器上,究竟变了什么

回忆一下小学做加法:把两个数右对齐,从个位开始,每位相加,超过九就写个零头,进一位到左边。这套流程有三个隐含前提:数字是右对齐的、我们从低位往高位走、进位最多是一。

链表表示整数时,恰好可以把这个前提原样搬过来。如果约定头节点存个位(低位在前),那么遍历链表的方向就是从个位到高位,跟竖式的计算方向完全一致。两个链表同时从头走,就相当于两个数字的指针在竖式里右对齐地同步左移。进位变量carry就是竖式里写在右上角那个小小的“1”。

所以这道题从来不是“设计一个新算法”,而是“把一个人人都会的流程,精确地翻译成指针代码”。翻译得好不好,取决于你对空指针、循环终止条件、哨兵节点的把控。这也是为什么很多人看题解觉得“就这?”,自己动手写却总有几个用例过不去。

1.3 需要什么前置基础,小白能不能直接上手

能。真正需要的前置知识只有三样:知道链表节点长什么样(一个值 + 一个指向下一个节点的指针)、会写链表的遍历循环、理解取模%和整除//的关系。这三样会了,加减法的主体逻辑你就能看懂八九成。

剩下的一成是工程习惯:用哨兵(dummy)节点规避首节点特判、用局部变量缓存next指针防止断链、想清楚返回的到底是dummy还是dummy.next。这些不是算法知识,是手感,写三遍就有。

我建议的顺序是这样:先不管效率,用最直白的写法把两个链表转成数组、按竖式算完再建回链表,跑通逻辑;然后逼自己改成“一次遍历、不开额外数组”的原版实现;最后再去看正序存储的进阶版。这个顺序能让你清楚感受到链表这个结构到底在省什么、代价是什么。

2. 数据表示与方案选型:为什么是链表

2.1 链表、数组、字符串,三条路的真实差异

手里有一串超长数字,可选载体其实不少。字符串最省事,"123" + "456"这种操作在 Python 里一行就能搞定,但字符串是不可变的,逐位修改就意味着频繁切片拼接,效率很难看;数组随机访问快,但长度需要在开始就算好(结果最多比最长的那个多一位),而且如果要做插入删除就麻烦了;链表每个节点只存一位,天然带着“下一位在哪”的信息,遍历时推进指针就行,不需要下标。

更关键的是题目约束。很多版本明确写着“链表中的数字是逆序存储的,且不能把链表翻转成整数”。这句话堵死了“转成数字再相加”的捷径,逼你必须逐位处理。而且从工程角度看,链表表示有一个数组比不了的优点:天然的任意精度。数组你得预先估计长度,估短了要扩容,估长了浪费内存;链表用多少节点申请多少,加出来的结果多长就多长,正好贴合大数运算“长度不可预知”的特点。

当然链表也有代价。每一位都要额外存一个指针(64 位系统上就是 8 字节),空间常数比数组大;而且没有随机访问,想从高位往低位算就得先反转或者用栈。这就是后面进阶版要处理的问题。

2.2 低位在前的约定,决定了整道题的难度

链表的“方向”是这道题最容易被忽视的一个设计点。约定头节点是个位,那么从头遍历就是从低位到高位,跟竖式方向一致,代码写起来顺畅得不行;约定头节点是最高位,那么从头遍历是从高位到低位,跟竖式方向相反,你就得先把两条链走到底,想办法从尾部往回算。

有意思的是,很多教材和题库里,第一种约定被当作默认,因为它让加法变简单了。但从业务角度看,第二种其实更自然——人读数字是从高位读的,把结果直接正序打印出来也更符合直觉。所以真实项目里经常出现“内部用低位在前存储、展示时反转一次”这种折中方案。

我个人的习惯是:加法、减法、乘法这类从低位开始算的运算,一律低位在前;如果业务需要正序,就在输入和输出两端各做一次反转,把复杂度挡在边界外面,核心算法保持干净。这样写出来的代码,读一眼就知道在干什么。

2.3 三条实现路线:新建、原地、递归

同样是算加法,实现风格可以差很多,实际影响也不同。先把这三条路摊开对比一下。

实现路线核心做法空间开销优点风险点
新建结果链表用 dummy 节点,每次循环 new 一个节点挂上去O(max(m,n)) 额外节点不改动输入,逻辑最清晰,最推荐忘记处理最后的 carry,结果少一位
原地复用 l1直接把结果写回 l1 的节点,长度不够时接上 l2 的剩余部分O(1) 额外空间内存友好,适合长链表会破坏输入,长度不等时指针接错就断链
递归把 carry 当参数往下传,回溯时构结果O(max(m,n)) 调用栈代码短,天然处理低位到高位链表过长直接爆栈

选哪条,取决于你的场景。面试写题、工程里做一次性计算,我强烈建议第一条:新建结果链表,用哨兵节点。多加一点内存换来的是「不会因为改坏输入导致难以复现的 bug」,这笔账怎么算都划算。

原地复用那一版值得单独说一句。它的诱惑在于空间 O(1),但它把「输入」和「输出」两个角色混在了一起。一旦算法中途出错,你的原始数据就没了,调试时连对照样本都拿不到。我在做某次数据校验工具时就吃过这个亏:为了省内存原地复用,结果进位逻辑写错,把上游传进来的一条链表改得面目全非,问题又是在下游才暴露的,排查花了整整半天。后来统一改成新建结果链表,问题瞬间从“难以定位”变成“一眼可见”。

递归版本适合当作思维练习,理解“进位怎么沿着调用链传递”这件事非常有帮助,但生产代码里要谨慎。Python 默认递归深度大概一千层,链表长度只要上千节点就RecursionError;Java 默认栈大小也就几百 KB,深度几千层同样会崩。真要用递归,得先确认输入规模。

3. 核心逻辑拆解:一个进位变量撑起整个算法

3.1 循环为什么必须写三个条件

这是这道题最高频的翻车点,没有之一。很多人写循环条件写while (l1 && l2),两个链表都空了就退出。这错在哪?错在最后那个进位还没处理。举个最小的例子:5 + 5。两个链表各只有一个节点,第一轮加完,本位是 0,进位是 1,然后两条链都变成空,循环退出,返回结果0——正确答案是10。

所以循环条件必须是三选一:l1 非空、l2 非空、carry 非零。三个中任意一个成立就得继续走。这意味着当两条链表都走完但进位还是 1 的时候,循环会再多跑一轮,专门把这个进位做成一个值为 1 的新节点。写的时候不要背答案,就问自己:还有没有没处理完的信息?答案是进位,那就把它写进条件。

3.2 空指针怎么绕:哨兵节点 + 取值兜底

当两条链表长度不等时,短的那条会先走空。这时候再去读l1.val就是空指针异常。处理手法有两层。

第一层是取值兜底:a = l1 ? l1.val : 0,空的就当 0 处理,这是竖式里“高位补零”的直译。第二层是推进兜底:if (l1) l1 = l1.next,空的就别再往前走了,否则走到 null 之后再取 next 又要炸。

哨兵节点(dummy)解决的是另一个问题:结果的第一个节点。如果不设哨兵,你就得写一堆if (head == null) head = node; else tail.next = node;的特判,代码立刻脏一倍。加上一个值为 0 的假头节点,所有节点都统一挂在cur.next上,最后返回dummy.next就行。这个技巧在链表题里几乎是标配,成本是一个节点,收益是整段代码结构的统一。用完记得别把dummy本身返回出去,多出来的那个 0 会让结果凭空多一位。

3.3 进位为什么永远只有 0 和 1:一个归纳证明

有人会担心进位会不会超过 1,比如 9 + 9 + 9 = 27 之类的。不会。理由很直接:每一位的数字范围是 0 到 9,两个数相加再加上进位,最大值是 9 + 9 + carry。只要 carry 不超过 1,和就不超过 19,19 / 10 = 1,新的进位还是 1。

用归纳法说清楚:初始 carry 是 0,满足不超过 1。假设第 i 轮开始时 carry ≤ 1,那么本位和 s = a + b + carry ≤ 9 + 9 + 1 = 19,新的 carry = s / 10 ≤ 1。归纳成立。

这个结论的实际意义在于:你不需要用取模来算进位,carry = sum >= 10或者carry = sum / 10都对,甚至某些语言里写成布尔值转整数也行。更重要的是,它保证了你永远只需要在结果上多挂一个节点,不可能多挂两个。调度内存的时候心里有底。

顺带说一个反直觉的点:这道题不需要考虑负数。如果输入带负号,那就不是加法了,得先做符号判定,转成绝对值的加减法组合,这是后面变体章节要讲的内容。题目里默认非负,别自己给自己加戏。

4. 完整实现与逐行注释

4.1 节点定义和两条测试链表

先把基础设施搭好。节点就是一个值加一个指针,没什么可说的,但建链表的工具函数值得写一个,方便反复构造测试用例。

class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next def build(digits): """digits 按低位在前的顺序传入,例如 [3,4,2] 表示 243""" dummy = ListNode() cur = dummy for d in digits: cur.next = ListNode(d) cur = cur.next return dummy.next def dump(head): """把链表还原成字符串,方便肉眼核对,低位在前所以要反转""" out = [] while head: out.append(str(head.val)) head = head.next return ''.join(reversed(out))

build里也用了哨兵,这样不管传进来几位数字,代码都一样。dump做反转是因为存储顺序是低位在前,打印给人看得倒过来。

4.2 主算法:Python 版本

def add_two_numbers(l1, l2): dummy = ListNode(0) # 哨兵,避免首节点特判 cur = dummy # 结果链表的尾指针 carry = 0 # 进位,取值只可能是 0 或 1 while l1 or l2 or carry: a = l1.val if l1 else 0 b = l2.val if l2 else 0 total = a + b + carry carry = total // 10 cur.next = ListNode(total % 10) cur = cur.next l1 = l1.next if l1 else None l2 = l2.next if l2 else None return dummy.next

这段代码我逐块说一下意图。哨兵节点的作用是让cur.next = ...这一句在第一次循环时也能正常执行,省掉「结果为空怎么办」的判断。carry = total // 10和total % 10是一对,前者拿进位,后者拿本位,顺序不能反——如果先算本位再更新进位,进位就被新值覆盖了。推进指针那两行用条件表达式,是为了避免在 null 上取 next。

有个细节值得留意:Python 里l1.next if l1 else None其实可以直接写l1 and l1.next,返回的可能是None也可能是False,虽然在这个循环条件里效果一样,但类型不干净,我不推荐。写清楚一点,将来别人接手不用猜。

4.3 Java 与 C++ 版本的关键差异

Java 的结构几乎一一对应,区别在于类型系统和空判断写法:

class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { ListNode dummy = new ListNode(0); ListNode cur = dummy; int carry = 0; while (l1 != null || l2 != null || carry != 0) { int a = (l1 != null) ? l1.val : 0; int b = (l2 != null) ? l2.val : 0; int total = a + b + carry; carry = total / 10; cur.next = new ListNode(total % 10); cur = cur.next; if (l1 != null) l1 = l1.next; if (l2 != null) l2 = l2.next; } return dummy.next; } }

Java 这里有一个隐藏的坑:total用的是int,最大 19,绝对安全,但如果有人为了“保险”用long,反而会让自动装箱拆箱在某些写法里带来额外开销。这种题里int就够了,别过度设计。

C++ 版本需要注意的是内存管理。哨兵节点我用栈对象,返回的是它的next,栈对象销毁时不影响堆上的节点,这是安全的:

struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) {} }; ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { ListNode dummy(0); ListNode* cur = &dummy; int carry = 0; while (l1 || l2 || carry) { int a = l1 ? l1->val : 0; int b = l2 ? l2->val : 0; int total = a + b + carry; carry = total / 10; cur->next = new ListNode(total % 10); cur = cur->next; if (l1) l1 = l1->next; if (l2) l2 = l2->next; } return dummy.next; }

C++ 里真正要小心的是:如果你按值返回ListNode(比如写成ListNode addTwoNumbers(...)),返回时会复制,而next指针指向的还是原来那批节点,一旦原对象析构就变成悬垂指针。这类题一律返回指针,别图方便。

4.4 边界用例清单与实测记录

写完代码就得测,而且要用“能暴露问题”的用例,不是随手点两个正常数字。我列一份自己常用的清单,每次写完这类代码都会跑一遍。

用例输入期望输出考察点
基本对齐342 + 465807常规路径,无进位链
连续进位999 + 11000进位穿透整条链,结果多一位
长度悬殊1 + 999999910000000短链表提前走空
双零相加0 + 00不能返回空链表,也不能返回 00
单边为零0 + 1234512345一边完全为空
全九相加999 + 9991998最高位进位,结果位数等于较长者加一
长链各 1000 位全 91001 位检验递归版本是否会爆栈

实测里最容易翻车的是「双零相加」和「全九相加」。前者容易返回一个空指针(如果循环条件写错或者哨兵处理不当),后者容易少一位。我自己的习惯是:本地跑一遍上面这七条,再加一组随机对拍——随机生成两个长度 1 到 30 的数字串,用语言自带的整数算一遍,跟链表算法结果比,跑上一万组。这个对拍脚本大概二十行,写一次能用一辈子。

4.5 复杂度和内存开销算笔明白账

时间复杂度 O(max(m, n)),m 和 n 是两条链表的长度。每一轮循环处理一位,最多跑 max(m, n) + 1 轮(多出来的那一轮是最后的进位)。这里没有嵌套循环,没有额外排序,就是线性扫一遍。

空间复杂度分两种说法。结果本身占 O(max(m, n)) 个节点,这是必须的,不算在“额外空间”里;额外辅助空间在新建链表版本里是 O(1)——只有三个指针变量和一个进位整型变量。注意哨兵节点也算一个额外节点,但它是常数级,不影响量级。递归版本则要额外算上 O(max(m, n)) 的调用栈。

对比一下“转成整数再相加”的做法:如果数字在语言原生类型范围内,那当然是 O(1) 直接算最快;一旦超范围,用BigInteger之类的库,内部其实也是在用数组做逐位运算,本质跟链表一样,只是数组比链表缓存友好(连续内存,CPU 预取命中率高)。所以如果追求极致性能,数组或字符串是更好的载体;链表在这里的胜出理由是接口友好——原地追加节点,不需要预先知道结果长度。

5. 从加法延伸出去的几个变体

5.1 正序存储:反转、栈、递归三条路

题目一旦改成“头节点存最高位”,难度立刻上一档。因为竖式必须从低位开始,而你现在从链表头走是从高位开始的。三种破解思路。

第一种,先反转两条链表,算完再反转结果。逻辑最直白,缺点是要写两遍反转,而且会改动输入(除非你先深拷贝一份)。

第二种,用两个栈把数字压进去,再逐个弹出。压栈的顺序是高到低,弹出的顺序就是低到高,天然对齐竖式。

def add_in_order(l1, l2): s1, s2 = [], [] while l1: s1.append(l1.val); l1 = l1.next while l2: s2.append(l2.val); l2 = l2.next carry = 0 head = None while s1 or s2 or carry: a = s1.pop() if s1 else 0 b = s2.pop() if s2 else 0 total = a + b + carry carry = total // 10 node = ListNode(total % 10) node.next = head # 头插法,直接得到正序结果 head = node return head

这段代码最妙的地方是头插法。因为从低位往高位算,算出来的每一位依次是结果的个位、十位、百位,把它们每次插到链表头部,最后天然就是正序。省掉了反转结果这一步,代码短且不容易错。空间开销是 O(m+n) 的两个栈,可以接受。

第三种,递归到底再回溯。递归本身就是个栈,思想跟第二种一样,只是不用显式开数组。缺点是长度一大就爆栈,还是那句老话,看输入规模。

5.2 减法与借位:符号判定是不可跳过的一步

加法只进位,减法要借位,境界完全不同。核心难点在于:借位之后本位可能是负数。比如 12 - 8,个位 2 减 8 不够,向十位借一,本位变成 12 - 8 = 4,十位减一之后变 0。

def sub_lists(a, b): """a、b 都是低位在前,且前提是 a 的数值不小于 b""" dummy = ListNode(0) cur = dummy borrow = 0 while a or b: x = a.val if a else 0 y = b.val if b else 0 diff = x - y - borrow if diff < 0: diff += 10 borrow = 1 else: borrow = 0 cur.next = ListNode(diff) cur = cur.next a = a.next if a else None b = b.next if b else None return dummy.next

这里有个必须注意的收尾:算完之后结果的高位可能出现前导零。比如 100 - 99 = 001,链表的最高位会挂着两个多余的 0。工程代码里要加一步「去掉高位的零」,但要小心全零的情况——0 - 0 的结果是一个 0,不能被删空。我见过有人写while res and res.next and res.val == 0: res = res.next,就是为了保住最后那一位。

至于两个数谁大谁小,必须在进算法之前判断:先比位数(先把链走到头数长度),位数相同再从左到右逐位比较。判断完再决定是 a 减 b 还是 b 减 a,结果要不要加负号。这一步很多人想跳过,直接用绝对值相减再补符号,结果是错的——符号由谁大谁小决定,不能拍脑袋。

5.3 乘法与阶乘:双层循环与统一进位

乘法是这道题的“下一代变形”,复杂度直接升到 O(m*n)。竖式乘法的逻辑是:先用第二个数的每一位去乘第一个数,得到若干个部分积,再错位相加。翻译成代码就是双层循环,把每一位乘积累加到一个数组里。

def multiply(num1, num2): # num1、num2 是正常顺序的数字字符串 m, n = len(num1), len(num2) res = [0] * (m + n) # 结果最多 m+n 位 for i in range(m - 1, -1, -1): for j in range(n - 1, -1, -1): p1, p2 = i + j, i + j + 1 total = int(num1[i]) * int(num2[j]) + res[p2] res[p2] = total % 10 res[p1] += total // 10 # 去掉前导零 k = 0 while k < len(res) - 1 and res[k] == 0: k += 1 return ''.join(map(str, res[k:]))

这里p1 = i + j、p2 = i + j + 1的下标推导值得说清楚。num1 的第 i 位(从右数是个位是第 0 位)乘以 num2 的第 j 位,结果的贡献落在第 i+j 和 i+j+1 这个位置上,因为两个个位数相乘最多是两位数。把p2当本位、p1当高位,就是直接在结果数组里错位累加,省掉了“部分积”那一整个中间结构。

顺带说一句,这个写法里res[p1] += total // 10可能让res[p1]暂时超过 9,不要紧,后面会有更高位继续往上累加,最后从左往右统一进位。如果非要中途保持每一位都是 0 到 9,就得写成内层再嵌一个进位循环,代码复杂度反而上升。

阶乘是高精度乘法的经典应用,思路是:维护一个结果数组,从 2 开始逐个数去乘。每次乘一个不超过当前结果长度的整数,从低位到高位遍历,边乘边进位,最后刷新有效长度。这个套路在教学里出现频率极高,因为它把“高精度 × 单精度”这个更简单的子问题单独拎出来练。

5.4 几个能顺手练的变形

同一套思维还能解不少题。比如“两条链表表示的数字做比较”,本质就是逐位比较,跟字符串比大小一样,先比长度再比字典序;“链表表示的数字加一”,是加法的超简化版,但从尾往头传进位的那条线跟正序加法一模一样;“回文链表”判断,常规解法是快慢指针找中点加反转后半段,跟这里的反转技巧是同一批基本功。

我的建议是:把加法写熟之后,挑减法和乘法各写一遍,基本就能摸到这类“用简单结构表示复杂数值”问题的通用套路了——拆位、对齐、单向扫描、进位/借位独立成变量、最后处理残留。这五步几乎能套到所有高精度运算上。

6. 踩坑记录与排查手册

6.1 高频错误速查表

这道题的错误其实高度集中,我把这些年见过和自己犯过的整理成一张表,写完代码可以对着扫一眼。

现象根因修复方式
结果为 0,期望是 10(5+5)循环条件漏了carry条件写成 `l1
空指针异常在 null 上取.val或.next取值和推进都加空判断
结果多一位 0返回了dummy而不是dummy.next检查返回语句
结果少最高位最后一次进位没做成节点确认循环条件包含 carry
长度不等时后半段丢失推进指针时直接l1 = l1.next未判空用if (l1)包一层
输入被改坏用了原地复用版本改新建结果链表
超长用例崩溃递归版本爆栈改迭代,或先确认输入规模
正序版结果顺序反了头插法写成了尾插结果节点要插到 head 前面

6.2 调试手法:打印中间态比单步强

链表题最烦的是出错时你看不出错在哪,因为链表没法直接打印。我的做法是在循环里临时插一行日志,把每一轮的a、b、carry、total都打出来,跟手算的竖式逐步对照。哪一轮开始偏的,问题就在那一轮。

print(f"round: a={a} b={b} carry_in={carry} total={total} -> digit={total % 10} carry_out={total // 10}")

另一个好习惯是先用一位数、两位数这种极小的用例跑通,再上长链。很多人喜欢一上来就测三百位的数字,结果错了根本不知道从哪查。从 1+1 开始,到 9+9,到 99+1,逐步加难度,每一步都知道该看什么。

还有就是对拍。写一个转换函数,把链表转成 Python 原生整数(在位数不大的前提下),随机生成一万组小数据,用原生整数和链表算法各算一遍,比对。这个方法能抓到所有边界问题,而且成本极低。我在写乘法那段代码时就是靠对拍揪出来的:前导零没处理好,0 * 123返回了空串。

6.3 工程与面试里的取舍

面试场景下,我建议先给新建链表的迭代版本,把三个循环条件和哨兵节点讲清楚,然后主动提一句“如果想省空间可以原地复用,但会破坏输入,一般不建议”。这一句话能体现出你考虑过 trade-off,比闷头写完强。

工程场景下,考虑的东西要多得多。第一,输入规模到底多大?如果是几百位,链表完全够用;如果是几十万位,指针带来的内存和缓存开销就很可观了,应该换成数组或者定长块状结构。第二,性能热点在哪?如果运算是高频调用的,那就得考虑内存池或者对象复用,避免每次加法都 new 一堆节点。第三,要不要用语言自带的库?Python 和 Java 的大整数实现经过高度优化,除非你有特殊需求(比如要严格的内存控制),否则没必要自己造。自己写的价值在于教学和特定场景,不是替代标准库。

最后说一个特别实用的心得:给这类函数配一个 debug 版本的打印工具。链表的数据结构调试成本高,一个能把链表可视化成十进制字符串的函数,能省掉大量时间。上面那个dump函数,我几乎是跟着算法一起写的,到现在还在用。

我个人在实际操作中的体会是,链表相加这种题,第一次写能过是运气,写到第三遍才算真的掌握。它不难在算法,难在你得同时盯住三条线——两条输入链的推进、进位的传递、结果链的构建。任何一条线分神,bug 就来了。我在带新人的时候通常会让对方先把代码手写一遍(不在编辑器里),然后再上机跑测试。手写的过程会强迫你想清楚每一个指针的归属,比看十遍题解都管用。真要练手,不妨试试给别人讲一遍:讲到“为什么循环条件里必须有 carry”这一句能讲明白,这题就算过关了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询