跳转至

BM86 大数加法

中等通过率 38.96%python3核心代码模式样例通过牛客 AC字符串模拟

牛客原题  源码

讲解章节:高精度与大整数

一句话

两串末尾对齐往前逐位相加,进位随手带到下一位。

解题思路

这题考什么

竖式加法的机器化表达。Python 的 int 是任意精度的, str(int(s or 0) + int(t or 0)) 一行就能通过;仍然手写竖式, 是因为这题真正考的是「位数超过机器字长时怎么加」—— C/C++/Java 没有内建的大整数类型,面试问的就是下面这套流程; 而同一副骨架换一条进位规则,就分别是大数乘法、进制转换与字符串减法。

竖式的三条规矩:

1. 低位对齐 —— 十进制的个位在字符串**末尾**,两个指针都从末尾起步
2. 逐位相加 —— cur = a 的当前位 + b 的当前位 + carry
3. 拆分进位 —— 本位写 cur % 10,进位 cur // 10 留给下一位

走一遍 "987" + "45":

i/j 指向        a位 b位 carry  cur   写下  新 carry
7 / 5            7   5    0     12     2      1
8 / 4            8   4    1     13     3      1
9 / 越界         9   0    1     10     0      1
越界 / 越界      0   0    1      1     1      0    <- 循环条件带上 carry
                               低位在前攒出 "2301",整体反转得 "1032"

短串先越界,缺位当 0 补,不必先把两串补齐成等长,省一次拷贝。 高精度的完整讨论见 高精度与大整数。

数据规模与复杂度

两串长度各 <= 1e5,仅由 "0"~"9" 构成,时限「其他语言 2 秒」, 题面要求时间 O(n)。本解每位做一次加法与一次除模, 时间 O(n + m)、空间 O(max(n, m))(结果串本身)。 真正会超时的是在循环里写 res = ch + res:字符串不可变, 每次前插都要重新分配并拷贝整串,总搬运量约 1e5 * 1e5 / 2 = 5e9 个字符。 用列表收集、最后一次性 "".join 才是线性的。

坑在哪

  1. 循环条件是 i >= 0 或 j >= 0 或 carry,三者取或。漏掉 carry 会丢掉最高位的 进位("99" + "1" 输出成 "00");把 or 误写成 and,短串一走完循环就停, 长串剩下的高位会被整段丢掉。
  2. 结果是从低位往高位攒出来的,末尾必须整体反转,否则输出是逆序的数字串。
  3. 空串是合法输入(样例 2 是 "114514" + ""),按 0 处理。 即便不写开头那两个短路,「越界补 0」的逻辑本身也已覆盖这种情况。
  4. ord(c) - 48 与 chr(x + 48) 是字符与数值之间的互转,48 是字符 "0" 的 ASCII 码。 这里用它而不是 int(c) / str(x) 纯属常数优化,行为完全一致; 偏移量写错一位,输出就整体错位成别的字符。
  5. 进位只可能是 0 或 1:两个个位数加一个进位最大是 9 + 9 + 1 = 19, 所以 cur // 10 不会超过 1,不需要循环处理多重进位。

参考实现

solutions/nowcoder/BM86/sol.py
from typing import List, Optional


class Solution:
    def solve(self, s: str, t: str) -> str:
        # 任一侧为空串按 0 处理:样例 2 就是 "114514" + ""
        if not s:
            return t if t else "0"
        if not t:
            return s

        # 个位在字符串末尾,两个读指针都从末尾起步,往低位方向(下标减小)走
        i, j = len(s) - 1, len(t) - 1
        carry = 0
        out: List[str] = []
        # 三者取或:短串走完还要接着算长串,两串都走完还要吐出最后的进位
        while i >= 0 or j >= 0 or carry:
            a = ord(s[i]) - 48 if i >= 0 else 0      # 越界的高位补 0,48 是 "0" 的 ASCII 码
            b = ord(t[j]) - 48 if j >= 0 else 0
            cur = a + b + carry
            out.append(chr(cur % 10 + 48))           # 本位只留个位数
            carry = cur // 10                        # 进位交给下一位,取值只可能是 0 或 1
            i -= 1
            j -= 1
        out.reverse()                                # 攒的时候低位在前,输出前翻正
        return "".join(out)                          # 一次性拼接,避免 O(n^2) 的逐字符前插
[:octicons-arrow-left-16: BM85](BM85.md) [BM87 :octicons-arrow-right-16:](BM87.md)