力扣日记剑指 Offer II 002

1. 题目

LeetCode 剑指 Offer II 002. 二进制加法

1.1 题意

字符串模拟二进制加法

1.2 分析

直接做竖式模拟,可以不做字符串反转,用两个指针记录下标。得到的结果也可以不用反转,可以调整加法顺序(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

1.3 我的解法

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;
    }
};

1.4 学习题解反思

我的解法:
时间复杂度O(max{a.length(),b.length()}),
空间复杂度O(1)

不妨来对两种情况做个分析

#include<iostream>
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

2.4 bug日记

今天居然没bug。

2.4.1 运行偶发性

被偶发性情况误导了,幸亏自己查看了源码,差点误导。
所以这里使用不需要翻转的加法理论上少一次反转的遍历(省的时间不多但是)

2. 后记

仅分享自己的想法,有意见和指点非常感谢

猜你喜欢

转载自blog.csdn.net/qq_51204877/article/details/131262410