LeetCode 剑指 Offer II 002. 二进制加法
字符串模拟二进制加法
直接做竖式模拟,可以不做字符串反转,用两个指针记录下标。得到的结果也可以不用反转,可以调整加法顺序(s = “0” + s和s = s + "0"的区别)。
但事实上它的速度受到string类实现的影响,如果string中存放char的数据结构是vector这种类型的的话速度,s = “0” + s需要拷贝,s = s + "0"方式可以直接push_back,差异会大很多。
// 这个是使用每次把新的结果位加到头部,结果不用反转
执行用时:4 ms, 在所有 C++ 提交中击败了56.52%的用户
内存消耗:6.8 MB, 在所有 C++ 提交中击败了12.12%的用户
通过测试用例:294 / 294
// 这个是使用每次把新的结果位加到尾部,结果需要反转
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:8 MB, 在所有 C++ 提交中击败了5.00%的用户
通过测试用例:294 / 294
class Solution {
public:
string addBinary(string a, string b) {
// 直接双指针
int n = a.length(), m = b.length();
int ptrA = n - 1, ptrB = m - 1;
string res = "";
int flag = 0; // 标志进位
while(ptrA >= 0 || ptrB >= 0){
// 没加完
if(ptrA < 0){
// 剩下b
int bit = (b[ptrB] - '0') ^ flag; // 当前位
flag = (b[ptrB--] - '0') & flag; // 进位
// 这个res + 的顺序是精髓
// 但是速度受限
res = char(bit + '0') + res;
}
else if(ptrB < 0){
// 剩下a
int bit = (a[ptrA] - '0') ^ flag;
flag = (a[ptrA--] - '0') & flag;
res = char(bit + '0') + res;
}
else{
int bit = (a[ptrA] - '0') ^ (b[ptrB] - '0') ^ flag;
flag = ( ( (a[ptrA--] - '0') + (b[ptrB--] - '0') + flag ) >=2);
res = char(bit + '0') + res;
}
}
// 仍然有进位
if(flag){
res = "1" + res;
}
return res;
}
};
我的解法:
时间复杂度O(max{a.length(),b.length()}),
空间复杂度O(1)
不妨来对两种情况做个分析
#include
using namespace std;
string s;
int main(){
s= s + "0";
s= "0" + s ;
}
情况一:s= s + "0";
// 通过vscode的ctrl跳转功能,查找到该情况对应使用的重载函数
// 如下
// 其中if判断的是左边字符串剩余的长度是否足够
// 不足跳转 _Xlen_string();
/*
打印提示信息的
[[noreturn]] inline void _Xlen_string() {
_Xlength_error("string too long");
}
*/
// 左边字符串足够则使用{}对应的构造函数
template <class _Elem, class _Traits, class _Alloc>
_NODISCARD basic_string<_Elem, _Traits, _Alloc> operator+(
const basic_string<_Elem, _Traits, _Alloc>& _Left, _In_z_ const _Elem* const _Right) {
using _Size_type = typename basic_string<_Elem, _Traits, _Alloc>::size_type;
const auto _Left_size = _Left.size();
const auto _Right_size = _Convert_size<_Size_type>(_Traits::length(_Right));
if (_Left.max_size() - _Left_size < _Right_size) {
_Xlen_string();
}
return {_String_constructor_concat_tag{}, _Left, _Left.c_str(), _Left_size, _Right, _Right_size};
}
// 这个构造函数有19+的overload
// 根据参数类型找到对应函数
// 可以看到底下有两个copy
// 也就是说他会把两个字符串都copy起来
basic_string(_String_constructor_concat_tag, const basic_string& _Source_of_al, const _Elem* const _Left_ptr,
const size_type _Left_size, const _Elem* const _Right_ptr, const size_type _Right_size)
: _Mypair(
_One_then_variadic_args_t{}, _Alty_traits::select_on_container_copy_construction(_Source_of_al._Getal())) {
_STL_INTERNAL_CHECK(_Left_size <= max_size());
_STL_INTERNAL_CHECK(_Right_size <= max_size());
_STL_INTERNAL_CHECK(_Right_size <= max_size() - _Left_size);
const auto _New_size = static_cast<size_type>(_Left_size + _Right_size);
size_type _New_capacity = _BUF_SIZE - 1;
auto& _My_data = _Mypair._Myval2;
_Elem* _Ptr = _My_data._Bx._Buf;
auto&& _Alproxy = _GET_PROXY_ALLOCATOR(_Alty, _Getal());
_Container_proxy_ptr<_Alty> _Proxy(_Alproxy, _My_data); // throws
if (_New_capacity < _New_size) {
_New_capacity = _Calculate_growth(_New_size, _BUF_SIZE - 1, max_size());
const pointer _Fancyptr = _Getal().allocate(_New_capacity + 1); // throws
_Ptr = _Unfancy(_Fancyptr);
_Construct_in_place(_My_data._Bx._Ptr, _Fancyptr);
}
_My_data._Mysize = _New_size;
_My_data._Myres = _New_capacity;
_Traits::copy(_Ptr, _Left_ptr, _Left_size);
_Traits::copy(_Ptr + static_cast<ptrdiff_t>(_Left_size), _Right_ptr, _Right_size);
_Traits::assign(_Ptr[_New_size], _Elem());
_Proxy._Release();
}
情况二:s= "0" + s ;
// 通过vscode的ctrl跳转功能,查找到该情况对应使用的重载函数
// 如下
// 其中if判断的是右边边字符串剩余的长度是否足够
// 不足跳转 _Xlen_string();
template <class _Elem, class _Traits, class _Alloc>
_NODISCARD basic_string<_Elem, _Traits, _Alloc> operator+(
_In_z_ const _Elem* const _Left, const basic_string<_Elem, _Traits, _Alloc>& _Right) {
using _Size_type = typename basic_string<_Elem, _Traits, _Alloc>::size_type;
const auto _Left_size = _Convert_size<_Size_type>(_Traits::length(_Left));
const auto _Right_size = _Right.size();
if (_Right.max_size() - _Right_size < _Left_size) {
_Xlen_string();
}
return {_String_constructor_concat_tag{}, _Right, _Left, _Left_size, _Right.c_str(), _Right_size};
}
// 他最后跳转的构造函数和上面的情况相同
// 也就是说都是copy两个字符串
可以看到代码中,两个方式都是copy两个字符串,所以两个情况都是一样的。所以上面的运行情况只是偶然差异,重新run一遍慢的那个情况,发现也是0ms
// 这个是使用每次把新的结果位加到头部,结果不用反转
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:6.8 MB, 在所有 C++ 提交中击败了8.23%的用户
通过测试用例:294 / 294
今天居然没bug。
被偶发性情况误导了,幸亏自己查看了源码,差点误导。
所以这里使用不需要翻转的加法理论上少一次反转的遍历(省的时间不多但是)
仅分享自己的想法,有意见和指点非常感谢