二进制之和
题目描述
小U和小R喜欢探索二进制数字的奥秘。他们想找到一个方法,将两个二进制字符串相加并以十进制的形式呈现。这个过程需要注意的是,他们的二进制串可能非常长,所以常规的方法可能无法处理大数。小U和小R希望你帮助他们设计一个算法,该算法能在保证时间复杂度不超过O(n^2)的前提下,返回两个二进制字符串的十进制求和结果。
题目解析
题目很简单,就是计算两个二进制字符串的十进制和的大小,并返回其十进制和的字符串,要求时间复杂度不能高于O(n2)。我总共用了四种算法来解决他。这里我将展示其中的三种。
方法介绍
我使用的方法分别是模拟二进制加法、递归计算以及使用栈来计算。接下来我将按顺序一一展示。
首先是模拟二进制加法
模拟二进制加法
这个方法也是最原始的方法,通过模拟二进制加法的逐位运算来计算和。每次从末尾开始处理,考虑进位。计算的核心部分如下
while (i >= 0 || j >= 0 || carry) {
int sum = carry; // 当前进位
if (i >= 0)
sum += a[i--] - '0'; // 加上 a 的当前位
if (j >= 0)
sum += b[j--] - '0'; // 加上 b 的当前位
result.push_back(sum % 2 + '0'); // 当前位的结果
carry = sum / 2; // 计算进位
}
由于字符串索引的增长方向与实际计算的方向相反,所以这里是从末尾开始逐位加。使用中间值承接两位字符串每位的计算结果,并将计算结果进行模运算。最后将最终结果放到结果数组里面去,这样就得到了二进制字符串的相加的二进制结果。至于计算成十进制的方法,可以选择像我这里一样,将数组翻转在计算,也可以继续从末尾开始相加。最终呈现的结果如下。
代码演示
#include <iostream>
#include <string>
#include <algorithm>
std::string addBinary(std::string a, std::string b) {
int i = a.size() - 1;
int j = b.size() - 1;
int carry = 0;
std::string result;
// 从末尾开始逐位加
while (i >= 0 || j >= 0 || carry) {
int sum = carry; // 当前进位
if (i >= 0) sum += a[i--] - '0'; // 加上 a 的当前位
if (j >= 0) sum += b[j--] - '0'; // 加上 b 的当前位
result.push_back(sum % 2 + '0'); // 当前位的结果
carry = sum / 2; // 计算进位
}
std::reverse(result.begin(), result.end()); // 反转结果字符串
return result;
}
std::string solution(std::string binary1, std::string binary2) {
// 计算二进制和
std::string binarySum = addBinary(binary1, binary2);
// 将二进制和转换为十进制
long long decimalSum = std::stoll(binarySum, nullptr, 2);
// 返回十进制结果
return std::to_string(decimalSum);
}
int main() {
// 测试用例
std::cout << (solution("101", "110") == "11") << std::endl; // 5 + 6 = 11
std::cout << (solution("111111", "10100") == "83") << std::endl; // 63 + 20 = 83
std::cout << (solution("111010101001001011", "100010101001") == "242420") << std::endl; // 123456 + 582 = 242420
std::cout << (solution("111010101001011", "10010101001") == "31220") << std::endl; // 12345 + 1205 = 31220
return 0;
}
同样的方法这里还可以使用分别计算,即分开计算二进制字符串的值,在相加得到最终值,而且代码总体更简易,效率更高,具体代码如下
代码演示
#include < iostream >
using namespace std;
#include < string >
int getNum(string binary) {
int key = 1;
int res = 0;
for (int i = binary.length() - 1; i > -1; i--) {
if (binary[i] == '1') {
res += key;
}
if (i == 0) {
return res;
}
key *= 2;
}
}
string solution(string binary1, string binary2) {
int res1 = getNum(binary1);
int res2 = getNum(binary2);
int res = res1 + res2;
return to_string(res);
}
int main() {
// You can add more test cases here
cout << (solution("101", "110") == "11") << endl;
cout << (solution("111111", "10100") == "83") << endl;
cout << (solution("111010101001001011", "100010101001") == "242420") << endl;
cout << (solution("111010101001011", "10010101001") == "31220") << endl;
return 0;
}
再然后就是递归法计算二进制之和
递归法
递归方法通过从二进制字符串的末尾逐位处理,加上进位,直到所有位都处理完。递归函数的终止条件是当两个字符串都为空并且没有进位时。该方法的核心代码如下
代码演示
std::string addBinaryRecursive(std::string a, std::string b, int carry) {
if (a.empty() && b.empty() && carry == 0) {
return "";
}
// 获取当前位的值
int bit_a = a.empty() ? 0 : a.back() - '0';
int bit_b = b.empty() ? 0 : b.back() - '0';
// 当前位的总和
int total = bit_a + bit_b + carry;
// 当前位的值(模 2)
int current_bit = total % 2;
// 计算进位
carry = total / 2;
// 去掉末尾的字符
if (!a.empty()) a.pop_back();
if (!b.empty()) b.pop_back();
// 递归处理下一个位
return addBinaryRecursive(a, b, carry) + std::to_string(current_bit);
}
这里采用了递归计算二进制的和。每次处理两个字符串的末尾字符,并考虑进位。当两个字符串检查到所有位都已经处理完毕并且进位也处理完毕,就结束递归。
核心代码解释完毕,接下来展示完整代码这里为了方便,我使用了库函数进行简化代码。
代码演示
#include <iostream>
#include <string>
// 递归方法计算二进制相加
std::string addBinaryRecursive(std::string a, std::string b, int carry) {
if (a.empty() && b.empty() && carry == 0) {
return "";
}
// 获取当前位的值
int bit_a = a.empty() ? 0 : a.back() - '0';
int bit_b = b.empty() ? 0 : b.back() - '0';
// 当前位的总和
int total = bit_a + bit_b + carry;
// 当前位的值(模 2)
int current_bit = total % 2;
// 计算进位
carry = total / 2;
// 去掉末尾的字符
if (!a.empty()) a.pop_back();
if (!b.empty()) b.pop_back();
// 递归处理下一个位
return addBinaryRecursive(a, b, carry) + std::to_string(current_bit);
}
// 对外暴露的 solution 函数
std::string solution(std::string binary1, std::string binary2) {
// 递归求二进制和
std::string binarySum = addBinaryRecursive(binary1, binary2, 0);
// 转换为十进制
long long decimalSum = std::stoll(binarySum, nullptr, 2);
return std::to_string(decimalSum);
}
int main() {
// 测试用例
std::cout << (solution("101", "110") == "11") << std::endl; // 5 + 6 = 11
std::cout << (solution("111111", "10100") == "83") << std::endl; // 63 + 20 = 83
std::cout << (solution("111010101001001011", "100010101001") == "242420") << std::endl; // 123456 + 582 = 242420
std::cout << (solution("111010101001011", "10010101001") == "31220") << std::endl; // 12345 + 1205 = 31220
return 0;
}
这里我使用 了std::stoll 将二进制字符串转换为十进制整数。
最后是使用栈计算的方法
栈法
栈的方法是模拟逐位加法过程,通过栈来保存每一位的加法结果。每次加法完成后,将结果压入栈中,直到处理完所有位。接下来我将展示其核心代码加深理解
代码演示
std::string addBinaryWithStack(std::string a, std::string b) {
int i = a.size() - 1;
int j = b.size() - 1;
int carry = 0;
std::stack<char> resultStack;
// 从末尾开始逐位加
while (i >= 0 || j >= 0 || carry) {
int sum = carry; // 当前进位
if (i >= 0) sum += a[i--] - '0'; // 加上 a 的当前位
if (j >= 0) sum += b[j--] - '0'; // 加上 b 的当前位
resultStack.push(sum % 2 + '0'); // 当前位的结果
carry = sum / 2; // 计算进位
}
// 将栈中的二进制结果转为字符串
std::string result = "";
while (!resultStack.empty()) {
result += resultStack.top();
resultStack.pop();
}
return result;
}
解释如下 栈的使用:通过栈存储每一位的加法结果,栈的特点是后进先出(LIFO),因此处理完所有位后,可以通过弹出栈中的元素来得到从高位到低位的加法结果。
在每次加法时,考虑进位的影响。
最后将栈中的二进制数反转并返回。
核心代码解释完毕,接下来我将展示完整代码。与递归法相同的是,我同样使用了库函数简化代码,但功能相同。
#include <iostream>
#include <string>
#include <stack>
std::string addBinaryWithStack(std::string a, std::string b) {
int i = a.size() - 1;
int j = b.size() - 1;
int carry = 0;
std::stack<char> resultStack;
// 从末尾开始逐位加
while (i >= 0 || j >= 0 || carry) {
int sum = carry; // 当前进位
if (i >= 0) sum += a[i--] - '0'; // 加上 a 的当前位
if (j >= 0) sum += b[j--] - '0'; // 加上 b 的当前位
resultStack.push(sum % 2 + '0'); // 当前位的结果
carry = sum / 2; // 计算进位
}
// 将栈中的二进制结果转为字符串
std::string result = "";
while (!resultStack.empty()) {
result += resultStack.top();
resultStack.pop();
}
return result;
}
// 对外暴露的 solution 函数
std::string solution(std::string binary1, std::string binary2) {
// 使用栈方法求二进制和
std::string binarySum = addBinaryWithStack(binary1, binary2);
// 转换为十进制
long long decimalSum = std::stoll(binarySum, nullptr, 2);
return std::to_string(decimalSum);
}
int main() {
// 测试用例
std::cout << (solution("101", "110") == "11") << std::endl; // 5 + 6 = 11
std::cout << (solution("111111", "10100") == "83") << std::endl; // 63 + 20 = 83
std::cout << (solution("111010101001001011", "100010101001") == "242420") << std::endl; // 123456 + 582 = 242420
std::cout << (solution("111010101001011", "10010101001") == "31220") << std::endl; // 12345 + 1205 = 31220
return 0;
}
代码中使用了 std::stoll 将二进制字符串转换为十进制字符串并返回。
递归法与栈法的比较
1-代码结构与实现方式
-
递归法:
- 递归法通过不断调用函数来逐步解决问题,每一层递归处理二进制的最低位并向上递归,直到所有位都加完并返回最终结果。
- 在递归过程中,函数调用栈用于存储中间结果,因此它的过程会比迭代的栈法要多一些开销,尤其是在大数据情况下。
-
栈法:
- 栈法利用显式的栈结构(
std::stack)来存储中间结果。通过迭代的方式从低位向高位计算,最终将栈中的值逐个弹出构建结果。 - 栈法在逻辑上更直观,因为它避免了递归函数的调用开销,而是通过手动管理栈来处理数据。
- 栈法利用显式的栈结构(
2- 性能
-
递归法:
- 递归函数每调用一次都会占用栈空间,因此会涉及函数调用栈的开销,尤其是在递归深度较大时,可能会导致栈溢出(Stack Overflow)问题。
- 对于非常长的二进制字符串,递归法可能会在处理较大数据时遇到性能瓶颈。
-
栈法:
- 栈法在内存管理上通常更加高效,因为栈操作是显式的,避免了递归函数调用的额外开销。
- 由于使用了显式的栈结构,栈法通常在性能上更加稳定,并且能够处理更大的数据而不容易发生栈溢出。
3- 可读性与易理解性
-
递归法:
- 递归法代码简洁,思路清晰,特别适合表示那些自然递归的问题(例如二进制加法)。它能使问题的逻辑结构更加符合数学上的递归定义。
- 然而,对于不熟悉递归的人来说,理解递归的执行流程可能需要更多的思考,尤其是在多次调用的过程中。
-
栈法:
- 栈法更加直观,适合那些喜欢迭代思维的人。使用栈来存储数据使得整个过程更加显式和可控。
- 由于栈法是迭代的,它不会像递归那样需要理解栈的回退过程,通常对于一些初学者来说更容易理解。
4- 内存使用
-
递归法:
- 递归调用会占用额外的栈空间,每一次函数调用都会有自己的栈帧。
- 在极端情况下,递归调用深度过大时可能会导致栈溢出。
-
栈法:
- 栈法使用堆栈数据结构来显式存储中间结果,因此内存使用较为直接且可控。
- 只要管理得当,栈法的内存使用更为稳定。
5- 执行效率
-
递归法:
- 由于每一次递归调用都涉及函数调用和返回的过程,递归法的执行效率相对较低,尤其是在数据量大时。
-
栈法:
- 栈法通过迭代方式计算,避免了递归的开销,执行效率通常更高。
6- 栈溢出的风险
-
递归法:
- 当递归层数太多时,递归法可能会导致栈溢出,尤其在处理非常大的二进制数字时,这种风险更加明显。
-
栈法:
- 栈法没有递归调用,因此不存在递归栈溢出的风险。栈的溢出仅仅取决于栈中存储的数据量,但通常这比递归溢出要更加可靠。
7- 适用场景
-
递归法:
- 适合解决那些具有递归性质的算法问题,代码简洁且能清晰表达递归逻辑。在计算小规模数据时效果很好。
-
栈法:
- 适合处理大规模数据时,栈法能更稳定高效地执行,且能避免栈溢出的风险。对于需要显式管理中间结果的情况,栈法更加合适。
总结
- 递归法优点是代码简洁、易于理解,适合小规模数据和自然递归的问题,但可能会导致栈溢出或性能瓶颈。
- 栈法优点是稳定、高效,避免了递归带来的开销和栈溢出风险,适合处理较大数据