高精度乘法的两种实现方式

LeetCode原题

思路1:用加法模拟和计算的竖式来模拟乘法

  首先,在常用基础算法中,我已经总结过了高精度整数和短小的低精度整数的乘法模板,如果不用数组的话,实现如下:

string mul(const string& s, int b)
{
    string ret;
    int t = 0;
    int i = s.size() - 1;
    while (i >= 0 || t != 0)
    {
        t = b * (i >= 0 ? s[i--] - '0' : 0) + t;
        ret.push_back(t % 10 + '0');
        t /= 10;
    }
    while (ret.size() > 1 && ret.back() == '0') ret.pop_back();
    reverse(ret.begin(), ret.end());
    return ret;
}

  仿照竖式乘法,
高精度乘法的两种实现方式_第1张图片
  可以让一个长度短的数 s h o r t n u m shortnum shortnum从低位往高位依次与长度长的数 l o n g n u m longnum longnum相乘,这就是个高精度整数乘以低精度整数的问题;
  然后给获得的乘积结果向后增加它对应 s h o r t n u m shortnum shortnum当前位数-1个0,以达到类似竖式中后一行前进的效果,然后每轮与结果进行高精度加法,这个比较基础,也可以参考我的上面那篇博客。

class Solution {
public:
    /*用加法模拟乘法 让小的那个数的每一位去乘那个长的数 然后最低位尾部加0个0 次位尾部加1个0..
    最高位尾部加长度短的数的长度个数-1个0 然后每次都与结果相加即可*/

    /*加法函数*/
    string myadd(const string& num1, const string& num2)
    {
        int r1 = num1.size() - 1, r2 = num2.size() - 1;
        int carry = 0;
        string ret;
        while (carry != 0 || r1 >= 0 || r2 >= 0)
        {
            carry = carry + (r1 >= 0 ? (num1[r1--] - '0') : 0) 
            + (r2 >= 0 ? (num2[r2--] - '0') : 0);
            ret.push_back((carry % 10) + '0');
            carry /= 10;
        }
        reverse(ret.begin(), ret.end());
        return ret;
    }

    /*短数b乘以长精度数A*/
    string mul(const string& A, const int b)
    {
        string ret;
        int t = 0;
        int r = A.size() - 1;
        while (r >= 0 || t != 0)
        {
            t = (r >= 0 ? (A[r--] - '0') : 0) * b + t;
            ret.push_back((t % 10) + '0');
            t /= 10;
        }
        while (ret.size() > 1 && ret.back() == '0') ret.pop_back();
        reverse(ret.begin(), ret.end());
        return ret;
    }

    string multiply(string num1, string num2) 
    {
        if (num1 == "0" || num2 == "0") return "0";
        int size1 = num1.size(), size2 = num2.size();
        string longnum = num1;
        string shortnum = num2;
        /*获得谁是长的谁是短的*/
        if (size2 > size1) 
        {
            longnum = num2;
            shortnum = num1;
        }
        /*ret先设置为0*/
        string ret = "0";
        /*从短长度数的最低位开始*/
        for (int i = min(size1, size2) - 1; i >= 0; --i)
        {
            /*获得这一位对应的数字*/
            int b = shortnum[i] - '0';
            /*这一位和长的数相乘*/
            string ret1 = mul(longnum, b);
            /*根据位数补0*/
            int cnt = min(size1, size2) - 1 - i;
            while (cnt--) ret1.push_back('0');
            /*加到结果里头去*/
            ret = myadd(ret, ret1);
        }
        return ret;
    }
};

思路2:数学方法

  首先证明:设 n u m 1 num1 num1的位数是m, n u m 2 num2 num2的位数是n,则 n u m 1 ∗ n u m 2 num1 * num2 num1num2的位数不是 m + n m + n m+n就是 m + n − 1 m + n - 1 m+n1.
P r o o f : 显 然 当 n u m 1 和 n u m 2 都 取 最 小 值 1 0 m − 1 , 1 0 n − 1 时 , 乘 积 最 小 , 乘 积 的 位 数 最 少 当 n u m 1 和 n u m 2 都 取 最 大 值 1 0 m − 1 , 1 0 n − 1 时 , 乘 积 最 大 , 乘 积 的 位 数 最 大 对 于 最 小 的 情 况 , 它 们 的 乘 积 结 果 为 1 ∗ 1 0 m + n − 2 , 是 m + n − 1 位 ; 对 于 最 大 的 情 况 , 它 们 的 乘 积 结 果 为 1 0 m + n − 1 0 m − 1 0 n + 1 , 这 个 数 小 于 1 0 m + n 大 于 1 0 m + n − 1 所 以 这 个 数 是 m + n 位 , 证 毕 Proof:显然当num1和num2都取最小值10^{m-1},10^{n - 1}时,乘积最小,乘积的位数最少\\ 当num1和num2都取最大值10^{m}-1,10^{n}-1时,乘积最大,乘积的位数最大\\ 对于最小的情况,它们的乘积结果为1 * 10^{m + n - 2},是m+n-1位;\\ 对于最大的情况,它们的乘积结果为10^{m+n}-10^{m}-10^{n}+1,这个数小于10^{m+n}大于10^{m+n-1}\\ 所以这个数是m+n位,证毕\\ Proof:num1num210m1,10n1num1num210m1,10n1110m+n2,m+n1;10m+n10m10n+1,10m+n10m+n1m+n,
  所以可以提前开一个长度为 m + n m+n m+n的数组存储乘积结果,这里可以先开一个整形数组来存结果,这样进位可以到整形数组转化为string时考虑.
  另外就是一个巧妙的地方了,注意到 n u m 1 [ i ] num1[i] num1[i] n u m 2 [ j ] num2[j] num2[j]的乘积,如果默认乘积结果是 m + n m + n m+n位(如果不够则添加前置0),它们的乘积结果会对应在结果数组的 i + j + 1 i+j+1 i+j+1位置处,即
r e t a r r [ i + j + 1 ] + = n u m 1 [ i ] ∗ n u m 2 [ j ] retarr[i + j + 1] += num1[i] * num2[j] retarr[i+j+1]+=num1[i]num2[j]
  然后处理进位,可以从低位向高位进值/10,低位保留值%10;
  最后转化为字符串,注意看看有没有前导0即可。

class Solution {
public:
    string multiply(string num1, string num2) 
    {
        if (num1 == "0" || num2 == "0") return "0";
        int m = num1.size(), n = num2.size();
        /*利用最小的n1是10^(m-1) 最小的n2是10^(n - 1)
        最大的n1是10^m - 1 最大的n2是10^n - 1
        得结果的位数不是m + n - 1位就是m + n位
        所以存储结果用一个m + n长度的数组一定够*/
        vector<int> tmp(m + n);
        for (int i = 0; i < m; ++i)
        {
            for (int j = 0; j < n; ++j)
            {
                /*n1[i] * n2[j]的结果在tmp[i + j + 1]处 先不考虑进位 后续处理它*/
                tmp[i + j + 1] += (num1[i] - '0') * (num2[j] - '0');
            }
        }
        /*处理进位 从尾向前进整除10的值 每个数变为本来%10*/
        for (int i = m + n - 1; i >= 1; --i)
        {
            tmp[i - 1] += tmp[i] / 10;
            tmp[i] %= 10;
        }
        string ret;
        int i = (tmp[0] == 0 ? 1 : 0);
        while (i < m + n)
        {
            ret.push_back(tmp[i] + '0');
            ++i;
        }
        return ret;
    }
};

  另外,还有一种把高精度乘法看成多项式乘法,然后把多项式乘法进行卷积运算,然后对卷积运算采用快速傅里叶变换算法来加速卷积运算,大家有兴趣的话我会研究后给大家连载卷积和多项式有关的数学知识。

你可能感兴趣的:(LeetCode刷题,算法专栏,leetcode,算法,动态规划)