LCR 002-二进制求和

Raphael Liu Lv10

给定两个 01 字符串 ab ,请计算它们的和,并以二进制字符串的形式输出。

输入为 非空 字符串且只包含数字 10

示例 1:

**输入:** a = "11", b = "10"
**输出:** "101"

示例 2:

**输入:** a = "1010", b = "1011"
**输出:** "10101"

提示:

  • 每个字符串仅由字符 '0''1' 组成。
  • 1 <= a.length, b.length <= 10^4
  • 字符串如果不是 "0" ,就都不含前导零。

注意:本题与主站 67 题相同:https://leetcode-cn.com/problems/add-binary/

题目分析

考虑一个最朴素的方法:先将 a 和 b 转化成十进制数,求和后再转化为二进制数。利用 Python 和 Java 自带的高精度运算,我们可以很简单地写出这个程序:

[pre-Python3]
1
2
3
class Solution:
def addBinary(self, a, b) -> str:
return '{0:b}'.format(int(a, 2) + int(b, 2))
[pre-Java]
1
2
3
4
5
6
7
class Solution {
public String addBinary(String a, String b) {
return Integer.toBinaryString(
Integer.parseInt(a, 2) + Integer.parseInt(b, 2)
);
}
}

如果 a 的位数是 n,b 的位数为 m,这个算法的渐进时间复杂度为 O(n + m)。但是这里非常简单的实现基于 Python 和 Java 本身的高精度功能,在其他的语言中可能并不适用,并且在 Java 中:

  • 如果字符串超过 33 位,不能转化为 Integer
  • 如果字符串超过 65 位,不能转化为 Long
  • 如果字符串超过 500000001 位,不能转化为 BigInteger

因此,为了适用于长度较大的字符串计算,我们应该使用更加健壮的算法。

方法一:模拟

思路和算法

我们可以借鉴「列竖式」的方法,末尾对齐,逐位相加。在十进制的计算中「逢十进一」,二进制中我们需要「逢二进一」。

具体的,我们可以取 n = \max{ |a|, |b| \,循环 n 次,从最低位开始遍历。我们使用一个变量 carry 表示上一个位置的进位,初始值为 0。记当前位置对其的两个位为 a_i 和 b_i,则每一位的答案为 (\textit{carry} + a_i + b_i) \bmod{2,下一位的进位为 \lfloor \textit{carry} + a_i + b_i}{2} \rfloor。重复上述步骤,直到数字 a 和 b 的每一位计算完毕。最后如果 carry 的最高位不为 0,则将最高位添加到计算结果的末尾。

注意,为了让各个位置对齐,你可以先反转这个代表二进制数字的字符串,然后低下标对应低位,高下标对应高位。当然你也可以直接把 a 和 b 中短的那一个补 0 直到和长的那个一样长,然后从高位向低位遍历,对应位置的答案按照顺序存入答案字符串内,最终将答案串反转。这里的代码给出第一种的实现。

代码

[sol1-Java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public String addBinary(String a, String b) {
StringBuffer ans = new StringBuffer();

int n = Math.max(a.length(), b.length()), carry = 0;
for (int i = 0; i < n; ++i) {
carry += i < a.length() ? (a.charAt(a.length() - 1 - i) - '0') : 0;
carry += i < b.length() ? (b.charAt(b.length() - 1 - i) - '0') : 0;
ans.append((char) (carry % 2 + '0'));
carry /= 2;
}

if (carry > 0) {
ans.append('1');
}
ans.reverse();

return ans.toString();
}
}
[sol1-C++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
string addBinary(string a, string b) {
string ans;
reverse(a.begin(), a.end());
reverse(b.begin(), b.end());

int n = max(a.size(), b.size()), carry = 0;
for (size_t i = 0; i < n; ++i) {
carry += i < a.size() ? (a.at(i) == '1') : 0;
carry += i < b.size() ? (b.at(i) == '1') : 0;
ans.push_back((carry % 2) ? '1' : '0');
carry /= 2;
}

if (carry) {
ans.push_back('1');
}
reverse(ans.begin(), ans.end());

return ans;
}
};
[sol1-Golang]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func addBinary(a string, b string) string {
ans := ""
carry := 0
lenA, lenB := len(a), len(b)
n := max(lenA, lenB)

for i := 0; i < n; i++ {
if i < lenA {
carry += int(a[lenA-i-1] - '0')
}
if i < lenB {
carry += int(b[lenB-i-1] - '0')
}
ans = strconv.Itoa(carry%2) + ans
carry /= 2
}
if carry > 0 {
ans = "1" + ans
}
return ans
}

func max(x, y int) int {
if x > y {
return x
}
return y
}
[sol1-C]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void reserve(char* s) {
int len = strlen(s);
for (int i = 0; i < len / 2; i++) {
char t = s[i];
s[i] = s[len - i - 1], s[len - i - 1] = t;
}
}

char* addBinary(char* a, char* b) {
reserve(a);
reserve(b);

int len_a = strlen(a), len_b = strlen(b);
int n = fmax(len_a, len_b), carry = 0, len = 0;
char* ans = (char*)malloc(sizeof(char) * (n + 2));
for (int i = 0; i < n; ++i) {
carry += i < len_a ? (a[i] == '1') : 0;
carry += i < len_b ? (b[i] == '1') : 0;
ans[len++] = carry % 2 + '0';
carry /= 2;
}

if (carry) {
ans[len++] = '1';
}
ans[len] = '\0';
reserve(ans);

return ans;
}

复杂度分析

假设 n = \max{ |a|, |b| \。

  • 时间复杂度:O(n),这里的时间复杂度来源于顺序遍历 a 和 b。
  • 空间复杂度:O(1),除去答案所占用的空间,这里使用了常数个临时变量。

方法二:位运算

思路和算法

如果不允许使用加减乘除,则可以使用位运算替代上述运算中的一些加减乘除的操作。

如果不了解位运算,可以先了解位运算并尝试练习以下题目:

我们可以设计这样的算法来计算:

  • 把 a 和 b 转换成整型数字 x 和 y,在接下来的过程中,x 保存结果,y 保存进位。
  • 当进位不为 0 时
    • 计算当前 x 和 y 的无进位相加结果:answer = x ^ y
    • 计算当前 x 和 y 的进位:carry = (x & y) << 1
    • 完成本次循环,更新 x = answery = carry
  • 返回 x 的二进制形式

为什么这个方法是可行的呢?在第一轮计算中,answer 的最后一位是 x 和 y 相加之后的结果,carry 的倒数第二位是 x 和 y 最后一位相加的进位。接着每一轮中,由于 carry 是由 x 和 y 按位与并且左移得到的,那么最后会补零,所以在下面计算的过程中后面的数位不受影响,而每一轮都可以得到一个低 i 位的答案和它向低 i + 1 位的进位,也就模拟了加法的过程。

代码

[sol2-Python3]
1
2
3
4
5
6
7
8
class Solution:
def addBinary(self, a, b) -> str:
x, y = int(a, 2), int(b, 2)
while y:
answer = x ^ y
carry = (x & y) << 1
x, y = answer, carry
return bin(x)[2:]

复杂度分析

  • 时间复杂度:O(|a| + |b| + X \cdot \max ({|a| + |b|})),字符串转化成数字需要的时间代价为 O(|a| + |b|),计算的时间代价为 O(\max { |a|, |b| }),X 为位运算所需的时间,因为这里用到了高精度计算,所以位运算的时间不一定为 O(1)。
  • 空间复杂度:这里使用了 x 和 y 来保存 a 和 b 的整数形式,如果用 Python 实现,这里用到了 Python 的高精度功能,实际的空间代价是 O(|a| + |b|)。
 Comments
On this page
LCR 002-二进制求和