编程算法题分析--大数加法

Posted muzhipin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编程算法题分析--大数加法相关的知识,希望对你有一定的参考价值。

NC1 大数加法

题目

描述:以字符串的形式读入两个数字,编写一个函数计算它们的和,以字符串形式返回。

数据范围:s.length,t.length≤100000,字符串仅由\'0\'~‘9’构成

要求:时间复杂度 O(n)

示例1

输入:
"1","99"
返回值:
"100"
说明:
1+99=100

示例2

输入:
"114514",""
返回值:
"114514"

解题思路

  1. 从字符串的最后一个字符开始遍历,每次从两个字符串里分别取出一个字符,进行相加。
  2. 需要判断是否有下位数的进位,如果有需要加上进位。
  3. 如果结果 >= 10,需要进行取模运算,得到个位数的值,把个位数记录到结果字符串里,需要记录取模运算的结果。

第一版源码

    string solve_old(string s, string t) 
        // write code here
        // return std::to_string(std::stoi(s) + std::stoi(t));
        // string s = "999", t = "100";
        auto sit = s.rbegin();
        auto tit = t.rbegin();

        string res; // 考虑优先分配好空间?
        int carry = 0; // 记录进位

        // 考虑使用下标? 代码更简洁
        // 优先分配空间后,考虑从最后一个空间开始? 避免反转
        while (sit != s.rend() && tit != t.rend()) 
            int tempRes = (int)(*sit - 48) + (int)(*tit - 48) + carry;
            carry = tempRes / 10;
            sit++;
            tit++;
            res += (char)(tempRes % 10 + 48);
        

        while (sit != s.rend()) 
            int tempRes = (int)(*sit - 48) + carry;
            carry = tempRes / 10;
            res += (char)(tempRes % 10 + 48);
            sit++;
        

        while (tit != t.rend()) 
            int tempRes = (int)(*tit - 48) + carry;
            carry = tempRes / 10;
            res += (char)(tempRes % 10 + 48);
            tit++;
        

        if (carry != 0) 
            res += (char)(carry + 48);
        

        std::reverse(res.begin(), res.end()); // 反转

        return res;
    

可以看出,代码有很多重复的地方,我们进一步优化

存在的问题

  • 如果字符串很长的话,会动态的修改存储结果的字符串的大小
  • 我们在第一版使用了三个while循环, 存在重复代码.
  • 我们最后是进行了字符串反转的

解决思路

  • 提前分配好空间
  • 使用索引来代替迭代器
  • 由于提前分配好了空间, 我们可以把结果从结果字符串的最后一位空间开始向前存储

第二版源码

    string solve_old1(string s, string t) 
        // 数组最后以一个元素的下标
        int sindex = s.size() - 1;
        int tindex = t.size() - 1;

        // 计算最大长度
        int maxLen = sindex > tindex ? sindex + 1 : tindex + 1;

        string str(maxLen + 1,\'0\'); // 提前分配好空间,比最长多一,考虑进位
        int carry = 0;               // 记录进位
        while (sindex >= 0 || tindex >= 0) 
            int tempSum = 0; // 记录临时和
            if (sindex >= 0) 
                tempSum += s[sindex--] - \'0\';
            
            if (tindex >= 0) 
                tempSum += t[tindex--] - \'0\';
            
            tempSum += carry;
            carry = tempSum / 10;
            str[maxLen--] = tempSum % 10 + \'0\';
        

        if (carry != 0) 
            str[maxLen] = carry + \'0\';
         else 
            str.erase(maxLen, 1);
        

        return str;
    

感觉空间利用率不高,我们复用参数字符串的空间,不在自己去重新创建空间。

最终版

    string solve(string s, string t) 
        // 数组最后以一个元素的下标
        int sindex = s.size() - 1;
        int tindex = t.size() - 1;
        // 计算最大长度
        int endIndex = sindex > tindex ? sindex : tindex;
        string str = sindex > tindex ? s : t; // 合理利用已经存在的空间

        // string str(maxLen + 1, \'0\'); // 提前分配好空间,比最长多一,考虑进位
        int carry = 0;               // 记录进位
        while (sindex >= 0 || tindex >= 0) 
            int tempSum = 0; // 记录临时和
            if (sindex >= 0) 
                tempSum += s[sindex--] - \'0\';
            
            if (tindex >= 0) 
                tempSum += t[tindex--] - \'0\';
            
            tempSum += carry;
            carry = tempSum / 10;
            str[endIndex--] = tempSum % 10 + \'0\';
        

        // 最高位进位
        if (carry != 0) 
            str.insert(str.begin(), carry + \'0\'); // 在最前面插入
        

        return str;
    

51nod基础题感触(1005大数加法)

     这篇就作为算法学习这块的第一篇文章啦!之前一直想来写一下博客来着,但是自己太懒了,建模比赛后想多休息(玩)一会儿(很长时间),一直没写。最近总算是下定决定了!

     “的确是要开始写一写最近自己做题的感受了!”(超认真的!)

     直入正题!(由于才正式开始学习,理解有不足之处还请指正!)

      首先,遇到这样的题,如果不限制语言的话,抱着能快则快的心态,我们就用强大的Python就行了。实现起来也是十分地的简单,在这里,我就直接上代码啦!(这里我用的是Python3.6)

 

     a=input()#输入,这时候的a实际上还是字符串

      b=input()

      print(int(a)+int(b))#字符串转整形,进行加法运算

 

      这样来看,Python在处理大数方面真地很方便,如果条件允许,推荐用Python来解题。但是,凡是还是要“知其然,更知其所以然”呀!所以这里我就依着我个人看资料后的个人理解用C++为大家讲一下吧!

      我们的任务就是完成这样一个式子的计算

技术图片

      那么,我们大数要怎么计算呢?

      这里我们先从简单的情况出发——两个非负数相加。

      对此,我分为这样几个步骤:

   1)正向数组存储 2)反向切割转化 3)逐位相加  4)反向输出

      1)正向数组存储

      为了方便讲解,我们以数12356611(A)、48661613(B)为例。(实际的int的范围可以去百度了解下,这里我们假设这时候整形就爆了!:P)

      录入之后char数组的情况就是这样的,这里我们用str1承接A,str2承接B,A对应的位数为A_len,B对应的位数为B_len。

技术图片

技术图片

     2)反向切割转化

      得到str1、str2后,我们再将他们反向录入足够长的int数组中

技术图片

技术图片

     3)逐位相加

     我们需要由低位至高位逐项相加,并且,在计算的时候我们还需要设定一个变量CF来储存上次的进位信息,拿图中的例子来说:首先我们先让A[0]、B[0]相加,得到最开始的C[0],然后用语句CF = C[0]/10得到CF的进位值0,用语句C[0] =C[0]%10得到C[0]的值,然后,我们继续计算,让A[1]、B[1]与CF一同相加,我们得到了最开始的C[1],接着按照老办法,CF = C[1]/10得到新的CF,C[1] = C[1]%10……如此循环,直到计算到i = max(A_len,B_len) ,也就是说计算到“ 最大和数位数 + 1 ”的位。

     另外,这里还要注明一下的是C[i] 是预先初始化为0的!承接str1、str2的整形数组A、B也是经过初始化为0才能接受str1、str2的。

技术图片

     4)反向输出

      在这里,我们只需要“正确”反向输出经过处理的C数组内的内容就行了!虽然这段话看起来简单,但是想“正确输出”,没接触过的小白写程序的时候还是要多注意下这个问题——C的最高位在哪里?看到这个问题,如果不加思考,很容易就会写成“这还不简单?开头不就是很多0吗?从第max(A_len,B_len) + 1位开始,遇0不输出”。好了,这下可能该丢的0、不该丢的0都丢了。正确的想法应该是在前者的基础上改成“开始遇0不输出,但一遇到非0就停下来,然后找到了最高位的位置,按情况输出”。如果想更快点,可以就在计算和的时候给最后的逐位计算来个CF的特判,直接找到最高位。

      了解了两个非负数相加,我们再考虑更复杂的情况——两个整数相加。

      两个整数相加,情况一共有三种:两非负数相加;一非负、一负相加;两负相加。再想一想,两负数相加实际上就是两负数的绝对值相加再加上负号,而一非负、一负相加实际上就是先判断符号,再计算两数相减的绝对值,所以计算的模式实际上就是两种:非负数相加模式(也就是之前说的简单情况),非负数相减模式(新增的哦!)。

      接下来,继续分析大数非负数相减。这里我们的思路与非负数大数相加类似,但是还是有区别的!过程分为这样几步:

1)正向数组存储 2)反向切割转化 3)结果符号判断 4)逐位相减  5)反向输出

      在这里,为了有助于理解,我们用 1115545(A)、-1253564(B) 来进行讲解

      1)正向数组存储

      我们用str1、str2分别存储A、B。

技术图片

技术图片

      2)反向切割转化

      这里我们根据str1、str2中有无负号得到A、B的位数,它们分别为A_len,B_len。

技术图片

技术图片

      3)结果符号判断

      对于符号的判断,我们可以根据下方的伪代码来进行判断

if(A_len!= B_len)

       if(A_len>B_len)
     
        //A的长度大于B,A为正数,所以结果为正数,下接A-B大数减法
     

 else

       int flag=0;
       int i=A_len-1;
       for(;i>0;i--)
      
           if(A[i]>B[i])
         
            flag=1;//A比B大
         
     
         if(i<0)
     
        输出0;
        return 0;
  
        if(flag)
     
        //A-B>0,下接A-B大数减法
      
      else
     
       //A-B<0,下接B-A大数减法
      

 

      4)逐位相减

      这里,与小学数学减法计算类似,我们从低位开始向高位计算,同时用br存储借位信息,用于后续的计算。

      万事俱备,我们从 i=A_len - 1 开始计算。开始的时候,用A[6] - B[6]得到初始的C[6],然后检测C[6]是否小于0,结果不是,然后确定初始C[6]就是我们要求的C[6],继续计算C[5],同样的方法,我们得到C[5]为-1,我们发现C[5]是小于0的,所以需要借位,让C[4]减1,同时再让初始C[5]+10,得到的结果为9,接着继续计算,直到算出C[0]。同时,我们在计算的时候用一个整型变量rec记录不为零的位的下标,直到记录到最后一个非零位下标。

技术图片

      经过计算,我们得到如下结果。

技术图片

5)反向输出

      根据“结果符号判断”得出的结论进行符号输出,从下标为rec的C的数组元素开始逐个输出。

 

附C++代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int main ()

    int a[10005]=0,b[10005]=0,c[10005]=0,la,lb,lc,x=0,nega=0,add=0,flag=0,bigger=1;
  //数组a,b代表被加数  c代表和数   
    char a1[10005],b1[10005];  
 //方便输入以及进行倒序用
    gets(a1);
    gets(b1);
    if(a1[0]==-||b1[0]==-)//至少有一个为负
    
       if(a1[0]==-&&b1[0]==-)//均为负
       
             nega=1;  //均为负数标志
             add=1;   //类同两非负数相加
             la=strlen(a1)-1;
             lb=strlen(b1)-1;
           
       else //一个为负
       
             flag=1;
             add=0; //类同两非负数相减
       
    
    else if(a1[0]==0&&b1[0]==0) //均为0
            cout<<0;
    else  //均为非负
    
          la=strlen(a1);
                 lb=strlen(b1);
          for(int i=0;i<=la-1;i++)     //进行倒序a
          
            a[la-i]=a1[i]-0;
          
          for (int j=0;j<=lb-1;j++)   //进行倒序b
          
            b[lb-j]=b1[j]-0;
          
          lc=1;
          while(lc<=la||lc<=lb)   //进行加法进位
          
            c[lc]=a[lc]+b[lc]+x;
            x=c[lc]/10;
            c[lc]%=10;
            lc++;
          
            c[lc]=x;
          while(c[lc]==0)  //确定最高位
          
            lc--;
          
          for(int k=lc;k>=1;k--)   //注意存放在数组中的数是倒序的
          
            cout<<c[k];
          
            return 0;
       
        if(nega==1&&add==1)//均为负数
     
          for(int i=0;i<=la-1;i++)     //进行倒序a
        
            a[la-i]=a1[i+1]-0;
            //cout<<la-i<<" "<<a[la-i]<<"||";
        
        //cout<<endl;
        for (int j=0;j<=lb-1;j++)   //进行倒序b
        
            b[lb-j]=b1[j+1]-0;
            //cout<<lb-j<<" "<<b[lb-j]<<"||";
        
        //cout<<endl;
        lc=1;
        while(lc<=la||lc<=lb)   //进行加法进位
        
            c[lc]=a[lc]+b[lc]+x;
            x=c[lc]/10;
            c[lc]%=10;
            //cout<<lc<<" "<<c[lc]<<"||"<<endl;
            lc++;
        
        c[lc]=x;
        while(c[lc]==0)  //注意最高位为0要舍弃
        
            lc--;
        
        cout<<-;
        for(int k=lc;k>=1;k--)   //注意存放在数组中的数是倒序的
        
            cout<<c[k];
        
     
     if(flag==1&&add==0)
     
         if(a1[0]==-)  //a为负,b为正 
         
         
               int rec=0;
             la=strlen(a1)-1;
             lb=strlen(b1);
             for(int i=0;i<=la-1;i++)     //进行倒序A
            
                a[la-i]=a1[i+1]-0;
            //    cout<<la-i<<" "<<a[la-i]<<"||";
            
            //cout<<endl;
            for (int j=0;j<=lb-1;j++)   //进行倒序B
            
                b[lb-j]=b1[j]-0;
            //    cout<<lb-j<<" "<<b[lb-j]<<"||";
                
            //cout<<endl;
             
             if(la>lb) 
             
                  bigger=0;        //|a|大 
             
             else if(la<lb)
             
                   bigger=1;    //|b|大 
              
             else if(la==lb)
             
                 for (int q=la;q>=1;q--)
                 
                     //cout<<a[q]<<" "<<b[q]<<endl;
                     if(a[q]>b[q])
                     
                         bigger=0;
                         break;
                     
                    else if(a[q]<b[q])
                     
                         bigger=1;
                         break;
                     
                 
             
            
            if(bigger==0)   //a更大 
            
                for(int q=1;q<=la;q++)
                
                    a[q]-=b[q];                    if(a[q]<0)
                    
                        a[q]+=10;
                        a[q+1]--;
                      
                    if(a[q]!=0)
                    rec=q;
                
                if(rec!=0)
                
                    cout<<"-";
                    for(int q=rec;q>=1;q--)
                    
                        cout<<a[q];
                        
                
                else
                
                    cout<<0;
                 
            
            else if(bigger==1)  //b更大 
            
                for(int q=1;q<=lb;q++)
                
                    b[q]-=a[q];
                
                    if(b[q]<0)
                    
                        b[q]+=10;
                        b[q+1]--;
                     
                     if(b[q]!=0)
                    rec=q;
                
                if(rec!=0)
                
                    for(int q=rec;q>=1;q--)
                    
                        cout<<b[q];
                        
                
                else
                
                    cout<<0;
                 
            
        
         else if(b1[0]==-)    //a为正,b为负 
        
            int rec=0;
             la=strlen(a1);
             lb=strlen(b1)-1;
             for(int i=0;i<=la-1;i++)     //进行倒序a
            
                a[la-i]=a1[i]-0;
                //cout<<la-i<<" "<<a[la-i]<<"||";
            
            //cout<<endl;
            for (int j=0;j<=lb-1;j++)   //进行倒序b
            
                b[lb-j]=b1[j+1]-0;
                //cout<<lb-j<<" "<<b[lb-j]<<"||";
                
            //cout<<endl;
             
             if(la>lb) 
             
                  bigger=0;        //|a|大 
             
             else if(la<lb)
             
                   bigger=1;    //|b|大 
              
             else
             
                 for (int q=la;q>=1;q--)
                 
                     //cout<<a[q]<<" "<<b[q]<<endl;
                     if(a[q]>b[q])
                     
                         bigger=0;
                         break;
                     
                     else if(a[q]<b[q])
                     
                         bigger=1;
                         break;
                     
                 
             
            if(bigger==0)   //a更大 
            
                    
                for(int q=1;q<=la;q++)
                
                    a[q]-=b[q];
                
                    if(a[q]<0)
                    
                        a[q]+=10;
                        a[q+1]--;
                      
                    if(a[q]!=0)
                    rec=q;
                
                if(rec!=0)
                
                
                    for(int q=rec;q>=1;q--)
                    
                        cout<<a[q];
                        
                
                else
                
                    cout<<0;
                 
            
            else if(bigger==1)  //b更大 
            
            
                for(int q=1;q<=lb;q++)
                
                    b[q]-=a[q];
                
                    if(b[q]<0)
                    
                        b[q]+=10;
                        b[q+1]--;
                     
                    if(b[q]!=0)
                    rec=q; 
                
                if(rec!=0)
                
                    cout<<"-";
                    for(int q=rec;q>=1;q--)
                    
                        cout<<b[q];
                        
                
                else
                
                    cout<<0;
                 
            
            
        
     
    
        return 0;
                        

 

以上是关于编程算法题分析--大数加法的主要内容,如果未能解决你的问题,请参考以下文章

51nod基础题感触(1005大数加法)

1005 大数加法

大数加法

49. 搜狗面试题: 大数相乘算法

51 Nod 1005 大数加法Java大数乱搞,python大数乱搞

51nod 1005 大数加法