LeetCode算法日记 - Day 34: 二进制求和、字符串相乘
目录
1. 二进制求和
1.1 题目解析
1.2 解法
1.3 代码实现
2. 字符串相乘
2.1 题目解析
2.2 解法
2.3 代码实现
1. 二进制求和
67. 二进制求和 - 力扣(LeetCode)
给你两个二进制字符串 a
和 b
,以二进制字符串的形式返回它们的和。
示例 1:
输入:a = "11", b = "1" 输出:"100"
示例 2:
输入:a = "1010", b = "1011" 输出:"10101"
提示:
1 <= a.length, b.length <= 104
a
和b
仅由字符'0'
或'1'
组成- 字符串如果不是
"0"
,就不含前导零
1.1 题目解析
题目本质:
把两个仅含 0/1
的长字符串当成二进制大整数,相加并以二进制字符串返回。本质就是“逐位相加 + 维护进位”。
常规解法:
把二进制转成整型/长整型相加,再转回二进制。
问题分析:
长度可达 10^4
,超出整型范围;而且频繁进制转换不必要、易溢出。应当直接在字符串层面做“竖式加法”。预计最佳复杂度:一次线性扫描 O(n)。
思路转折:
要想高效且安全 → 不做数值转换 → 双指针从右到左逐位相加,sum = carry + bitA + bitB,当前位写 sum % 2,新的进位 sum / 2
这样无需补零,只要在循环里判断指针是否越界即可;最后把构建的结果反转即可得到正确顺序。
1.2 解法
算法思想:设 i = a.length()-1、j = b.length()-1、carry = 0。循环条件:i>=0 || j>=0 || carry!=0。 每轮:
-
sum = carry + (i>=0 ? a[i]-'0' : 0) + (j>=0 ? b[j]-'0' : 0)
-
结果位:sum % 2
-
新进位:sum / 2
i)置双指针到两串末尾,carry=0,准备 StringBuilder(可预分配容量 maxLen+1)。
ii)循环:只要任一串未遍历完或还有进位,就继续:
-
读各自当前位(越界当 0);
-
相加得 sum;
-
append(sum % 2) 到 sb(此时是低位在前);
-
carry = sum / 2;指针左移。
iii)循环结束后 sb.reverse() 得到最终结果。
易错点:
-
while 条件必须是 carry != 0,不能写成 carry >= 0,否则可能死循环。
-
charAt(i) 是字符,要先减 '0' 转为数值位。
-
从右到左加;别从左往右。
-
注意不等长字符串:用 if (i>=0)/if (j>=0) 判断是否还能取位。
-
StringBuilder.append(int) 对 0/1是安全的;也可写 append((char)('0' + (sum & 1)))。
-
别在循环里频繁字符串拼接
+
,会产生大量中间对象,影响性能。
1.3 代码实现
class Solution {public String addBinary(String a, String b) {int i = a.length() - 1, j = b.length() - 1;int carry = 0;// 预估容量:最大长度 + 1(可能多出一个进位)StringBuilder sb = new StringBuilder(Math.max(a.length(), b.length()) + 1);while (i >= 0 || j >= 0 || carry != 0) {int sum = carry;if (i >= 0) sum += a.charAt(i--) - '0';if (j >= 0) sum += b.charAt(j--) - '0';sb.append(sum % 2); // 当前位carry = sum / 2; // 新进位}return sb.reverse().toString();}
}
复杂度分析:
-
时间:O(n),其中 n = max(|a|, |b|),每位最多访问一次。
-
空间:O(n),StringBuilder 存放结果(最坏多一位进位)。
2. 字符串相乘
43. 字符串相乘 - 力扣(LeetCode)
给定两个以字符串形式表示的非负整数 num1
和 num2
,返回 num1
和 num2
的乘积,它们的乘积也表示为字符串形式。
注意:不能使用任何内置的 BigInteger 库或直接将输入转换为整数。
示例 1:
输入: num1 = "2", num2 = "3" 输出: "6"
示例 2:
输入: num1 = "123", num2 = "456" 输出: "56088"
提示:
1 <= num1.length, num2.length <= 200
num1
和num2
只能由数字组成。num1
和num2
都不包含任何前导零,除了数字0本身。
2.1 题目解析
题目本质:
把两个十进制数字串当作“大整数”做乘法,返回其十进制字符串。可抽象为“竖式乘法在字符串层面的模拟”。
常规解法:
直接把 num1/num2 转为内置整数或 BigInteger 再相乘,最后转回字符串。
问题分析:
数据长度可达 200,超出 64 位整数范围;题目也禁止用大整数库或直接转整型。必须在字符/数组层面完成乘法。理想复杂度为 O(mn)(m、n 为两字符串长度),空间 O(m+n)。
思路转折:
要想安全且高效 → 放弃内置数值运算 → 用“竖式乘法”:
-
两两相乘,按位权落在正确的“结果槽位”上;
-
把所有乘积先累加到对应槽位(相当于“卷积”);
-
最后统一处理进位,再生成字符串。
这样实现清晰、易于调试,也能稳定做到 O(mn)。
2.2 解法
算法思想:算法思想:
令 a = reverse(num1)、b = reverse(num2)(把最低位放前面),准备长度为 m+n-1 的整型数组 tmp 用于“无进位卷积”:
mp[i+j]+=(a[i]−′0′)⋅(b[j]−′0′)
然后从低到高扫描 tmp:
sum←carry+tmp[cur],输出(sum%10),carry←⌊sum/10⌋
若扫描完仍有 carry,继续“吐出”高位。最后反转得到正常顺序,并去掉可能出现的前导零。
i)特判:若任一输入为 "0"
,直接返回 "0"
。
ii)反转两个字符串并转为 char[]。
iii)申请int[] tmp(长度 m+n-1),双重循环累加 tmp[i+j] += (a[i]-'0')*(b[j]-'0')。
iiii)统一进位:用一个移动的 carry 从 tmp[0] 扫到 tmp[len-1],每步输出当前个位、更新进位;若 carry 仍非 0,继续输出直到为 0。
iiiii)删除将来会变成前导零的尾部 0(因为此时字符串是“低位在前”),再反转得到答案。
易错点:
-
卷积阶段必须是
+=
,不能写成 =(会覆盖之前累加)。 -
记得把字符转数字:ch - '0'。
-
结果桶长度为 m+n-1(卷积长度);统一进位阶段会把最高位的进位继续“吐出来”(所以最终位数≤m+n)。
-
统一进位的循环条件建议写成 while (cur < tmp.length || carry != 0);sum 初始化为 carry,再按需加 tmp[cur]。
-
去“前导零”的时机:在反转之前删尾部 0(至少保留 1 位,避免把 0 本身清空)。
2.3 代码实现
class Solution {public String multiply(String num1, String num2) {// 1) 边界:任一为 "0"if (num1.equals("0") || num2.equals("0")) return "0";int m = num1.length(), n = num2.length();char[] a = new StringBuilder(num1).reverse().toString().toCharArray();char[] b = new StringBuilder(num2).reverse().toString().toCharArray();// 2) 无进位卷积:长度 m + n - 1int[] tmp = new int[m + n - 1];for (int i = 0; i < m; i++) {int di = a[i] - '0';for (int j = 0; j < n; j++) {int dj = b[j] - '0';tmp[i + j] += di * dj; // 累加!!不是覆盖}}// 3) 统一进位:从低到高,把每格拆成 个位(输出) + 进位(带走)StringBuilder sb = new StringBuilder(m + n);int cur = 0, carry = 0;while (cur < tmp.length || carry != 0) {if (cur < tmp.length) carry += tmp[cur++];sb.append((char) ('0' + (carry % 10)));carry /= 10;}// 4) 去掉将来会成为“前导零”的尾部 0(至少保留1位)while (sb.length() > 1 && sb.charAt(sb.length() - 1) == '0') {sb.deleteCharAt(sb.length() - 1);}// 5) 反转得到正常顺序return sb.reverse().toString();}
}
复杂度分析:
-
时间:O(mn),双重循环累积卷积项,统一进位线性扫描一次。
-
空间:O(m+n),tmp 与输出字符串(最坏 m+n 位)。