编程算法题分析--大数加法
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"
解题思路
- 从字符串的最后一个字符开始遍历,每次从两个字符串里分别取出一个字符,进行相加。
- 需要判断是否有下位数的进位,如果有需要加上进位。
- 如果结果 >= 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;
以上是关于编程算法题分析--大数加法的主要内容,如果未能解决你的问题,请参考以下文章