大数加法(含小数)
大数加法(含小数)
题目描述
实现两个大数相加,大数可能包含小数位。输入两个数字字符串
numStr1、numStr2,返回它们的和的字符串表示。
数据范围:数字字符串长度可能非常大,无法用内置整数类型直接存储,必须用字符串模拟。
示例
输入:
9007199254740991
1234567899999999999
输出:
1243575099254740990输入:
9007199254740990.12
1234567899999999999
输出:
1243575099254740989.12核心思路
大数加法的本质:模拟竖式加法,从最低位(最右侧)往最高位逐位相加,处理进位。
含小数时的关键步骤:
- 拆分整数部分和小数部分:以
'.'为界切分; - 小数部分右补 0:让两个数的小数位长度一致,方便逐位对齐;
- 整数部分左补 0:让两个数的整数位长度一致;
- 拼接后从右往左逐位相加,用
carry处理进位; - 还原小数点:根据小数部分长度
n,在结果中切出整数部分和小数部分; - 处理前导 0 和末尾 0。
原始代码(62% 通过率)
class Solution:
def bigAdd(self, numStr1, numStr2):
if '.' in numStr1:
a0, a1 = numStr1.split('.')
else:
a0, a1 = numStr1, ''
if '.' in numStr2:
b0, b1 = numStr2.split('.')
else:
b0, b1 = numStr2, ''
# 小数部分右侧补 0
n = max(len(a1), len(b1))
a1 = a1.ljust(n, '0')
b1 = b1.ljust(n, '0')
# 整数部分左侧补 0
m = max(len(a0), len(b0))
a0 = a0.rjust(m, '0')
b0 = b0.rjust(m, '0')
aa = a0 + a1
bb = b0 + b1
N = m + n
ans = []
carry = 0
for i in range(N - 1, -1, -1):
x = int(aa[i])
y = int(bb[i])
total = x + y + carry
carry, cur = divmod(total, 10)
ans.append(str(cur))
if carry:
ans.append(str(carry))
ans.reverse()
res = ''.join(ans)
if n > 0:
int_part = res[:-n]
frac_part = res[-n:]
int_part = int_part.lstrip('0') or '0'
return int_part + '.' + frac_part
else:
res = res.lstrip('0') or '0'
return res原始代码的问题(为什么只有 62%)
问题 1:小数部分末尾多余的 0 没有去掉
反例:"1.5" + "2.5" = "4.0",代码返回
"4.0",但通常期望 "4"。
反例:"1.25" + "2.75" = "4.00",代码返回
"4.00",期望 "4"。
反例:"0.5" + "0.4" = "0.9" 返回
"0.9" 正确,但 "0.5" + "0.5" = "1.0" 返回
"1.0",期望 "1"。
问题 2:小数部分全 0 时小数点没有去掉
"1.00" + "2.00" = "3.00",应该返回
"3",而不是 "3.00"。
问题 3:整数部分为空字符串的边界(较少见但要注意)
输入 ".5" 时
a0 = "",如果两数整数部分都为空,m = 0,a0 = "",虽然代码能跑,但
res[:-n] 可能得到空串,靠 lstrip('0') or '0'
兜底,正确但依赖运气。
问题 4:多个小数点或非法输入
"1.2.3" 这种输入 split('.') 会返回 3
个元素,直接报错。题目一般不测,但严谨起见可以处理。
修正后的代码
class Solution:
def bigAdd(self, numStr1: str, numStr2: str) -> str:
# 1. 拆分整数部分和小数部分
if '.' in numStr1:
a0, a1 = numStr1.split('.', 1)
else:
a0, a1 = numStr1, ''
if '.' in numStr2:
b0, b1 = numStr2.split('.', 1)
else:
b0, b1 = numStr2, ''
# 2. 小数部分右补 0
n = max(len(a1), len(b1))
a1 = a1.ljust(n, '0')
b1 = b1.ljust(n, '0')
# 3. 整数部分左补 0
m = max(len(a0), len(b0), 1) # 至少 1 位,避免空串
a0 = a0.rjust(m, '0')
b0 = b0.rjust(m, '0')
aa = a0 + a1
bb = b0 + b1
N = m + n
# 4. 从右往左逐位相加
ans = []
carry = 0
for i in range(N - 1, -1, -1):
total = int(aa[i]) + int(bb[i]) + carry
carry, cur = divmod(total, 10)
ans.append(str(cur))
if carry:
ans.append(str(carry))
ans.reverse()
res = ''.join(ans)
# 5. 还原小数点
if n > 0:
int_part = res[:-n]
frac_part = res[-n:]
# 去掉整数部分的前导 0
int_part = int_part.lstrip('0') or '0'
# 去掉小数部分的末尾 0
frac_part = frac_part.rstrip('0')
if frac_part:
return int_part + '.' + frac_part
else:
return int_part
else:
return res.lstrip('0') or '0'修正点对照
| 问题 | 修正 |
|---|---|
| 小数末尾多余的 0 | frac_part = frac_part.rstrip('0') |
| 小数全 0 时残留小数点 | if frac_part: return int_part + '.' + frac_part; else: return int_part |
| 整数部分可能为空串 | m = max(len(a0), len(b0), 1) 保证至少 1 位 |
| 多个小数点 | split('.', 1) 只切第一个点 |
完整可运行代码
class Solution:
def bigAdd(self, numStr1: str, numStr2: str) -> str:
if '.' in numStr1:
a0, a1 = numStr1.split('.', 1)
else:
a0, a1 = numStr1, ''
if '.' in numStr2:
b0, b1 = numStr2.split('.', 1)
else:
b0, b1 = numStr2, ''
n = max(len(a1), len(b1))
a1 = a1.ljust(n, '0')
b1 = b1.ljust(n, '0')
m = max(len(a0), len(b0), 1)
a0 = a0.rjust(m, '0')
b0 = b0.rjust(m, '0')
aa = a0 + a1
bb = b0 + b1
N = m + n
ans = []
carry = 0
for i in range(N - 1, -1, -1):
total = int(aa[i]) + int(bb[i]) + carry
carry, cur = divmod(total, 10)
ans.append(str(cur))
if carry:
ans.append(str(carry))
ans.reverse()
res = ''.join(ans)
if n > 0:
int_part = res[:-n]
frac_part = res[-n:]
int_part = int_part.lstrip('0') or '0'
frac_part = frac_part.rstrip('0')
if frac_part:
return int_part + '.' + frac_part
else:
return int_part
else:
return res.lstrip('0') or '0'
if __name__ == '__main__':
s = Solution()
numStr1 = input().strip()
numStr2 = input().strip()
print(s.bigAdd(numStr1, numStr2))测试用例
| numStr1 | numStr2 | 期望输出 | 原始代码 | 修正后 |
|---|---|---|---|---|
"9007199254740991" |
"1234567899999999999" |
"1243575099254740990" |
✓ | ✓ |
"9007199254740990.12" |
"1234567899999999999" |
"1243575099254740989.12" |
✓ | ✓ |
"1.5" |
"2.5" |
"4" |
"4.0" ✗ |
"4" ✓ |
"1.25" |
"2.75" |
"4" |
"4.00" ✗ |
"4" ✓ |
"0.5" |
"0.5" |
"1" |
"1.0" ✗ |
"1" ✓ |
"0.5" |
"0.4" |
"0.9" |
✓ | ✓ |
"0.0" |
"0.0" |
"0" |
"0.0" ✗ |
"0" ✓ |
"0" |
"0" |
"0" |
✓ | ✓ |
"123.456" |
"0" |
"123.456" |
✓ | ✓ |
".5" |
".5" |
"1" |
"1.0" ✗ |
"1" ✓ |
复杂度分析
- 时间复杂度:,其中 。每位相加一次。
- 空间复杂度:,存储结果和补齐后的字符串。
关键细节
1. 为什么先补 0 再相加
- 小数部分右补 0:
"1.5"和"2.25",补成"1.50"和"2.25",小数点对齐; - 整数部分左补 0:
"12"和"3",补成"12"和"03",位数对齐。
补齐后,两个拼接字符串长度相等,可以直接从右往左逐位相加。
2. 进位在最高位时多一位
如果最后还有进位,需要额外追加一位。例如
"99" + "1" = "100",res 的长度比
N 多 1。这不会影响 res[:-n] 和
res[-n:] 的切分,因为进位加在整数部分的最前面。
3. 前导 0 和末尾 0 的处理
- 整数部分前导 0:
"001"→"1",用lstrip('0'),但要防止全 0 变成空串,用or '0'兜底; - 小数部分末尾 0:
"1.500"→"1.5",用rstrip('0'); - 小数部分全 0:
"1.000"→"1",rstrip('0')后为空串,不拼接小数点。
4. 空整数部分的处理
输入 ".5" 时整数部分为空串,如果直接
rjust(m, '0') 且
m = 0,结果还是空串,res[:-n] 可能得到空串。加
max(..., 1) 保证至少 1 位,避免边界问题。
5. 多个小数点的容错
numStr1.split('.', 1) 只切第一个
.,剩下的部分作为小数部分。如果输入 "1.2.3"
这种非法输入,小数部分会变成 "2.3",后续 int()
会报错。如果需要严格容错,可以在前面加校验,但题目一般不测。
总结
- 本质:字符串模拟竖式加法,处理进位;
- 拆分:以
.为界拆成整数部分和小数部分; - 对齐:小数部分右补 0,整数部分左补 0;
- 相加:从右往左逐位相加,
carry处理进位; - 还原:按小数部分长度切出整数和小数,去掉前导 0 和末尾 0;
- 易错点:小数末尾的 0 要去掉,小数全 0 时要去掉小数点;
- 通用套路:大数运算 → 字符串模拟竖式,按位处理进位。
相关题目
- LC 415. 字符串相加(整数大数加法)
- LC 43. 字符串相乘(大数乘法)
- LC 2. 两数相加(链表版大数加法)
- LC 67. 二进制求和(二进制大数加法)
- LC 989. 数组形式的整数加法(数组版大数加法)