目录
字符串的高精度加法
为什么需要高精度加法?
怎么进行高精度加法?
链表的高精度加法
翻转链表(带虚拟头节点)
字符串的高精度乘法
字符串的高精度加法
大数加法_牛客题霸_牛客网 (nowcoder.com)https://www.nowcoder.com/practice/11ae12e8c6fe48f883cad618c2e81475?tpId=196&tqId=37176&ru=/exam/oj
为什么需要高精度加法?
int 能表示的数的范围为 -2147483648() ~ 2147483647(),long long int 能表示的最大数为 ,即使一个数据类型能表示的数字再大,也没办法保存所有的数,在加减乘除的计算上也会有溢出的风险,为了减少溢出的风险,可以用 string 保存数字,再来进行计算。
怎么进行高精度加法?
在日常的加法中,我们让两个加数从右往左,从低位开始对齐,对齐后开始进行加法运算,个位和个位相加,十位和十位相加,如果相加之和大于10,则需要进位,如图所示:
高精度加法就是要模拟平时加法的过程。
假设相加得到的字符串为 ret,按照字符串的存储方式,字符串从左往右,对应数字从高位到低位,所以两个字符串都从后往前遍历,访问一个数字便相加,把相加的结果尾插到 ret 中,模拟每一位对齐后相加。
每一位数相加的结果如果大于等于10,就需要进位,那我们怎么得到进位的值?
我们不能单纯地让每一位数进行相加和尾插,我们需要定义一个变量 tmp ,来存储我们每一位数相加的和,tmp%10 就可以得到 tmp 的个位,即我们要尾插的数,尾插后 tmp/= 10,就可以得到进位的数,进行下一位的相加时,直接把每一位数相加到 tmp 中,便完成了进位操作。
在相加时,不可避免会遇到两个数的位数不一样,有的数4位,有的数2位,怎么处理位数不一样的情况?
在每一位相加前,我们需要判断当前的字符串的每一位是否已经相加结束,如果已经相加结束了,就不再访问这个字符串了。
因为我们是把和尾插到 ret 中,所以和也是反的,我们需要把 ret 进行 reverse,就可以得到最终结果。
class Solution {
public:
string solve(string s, string t) {
int i=s.size()-1;
int j=t.size()-1;
string ret;//两数相加之和
int tmp=0;//记录每一位相加的和
while(i>=0 || j>=0 || tmp)
{
if(i>=0) tmp+=(s[i--]-'0');//该位不是空,才可以相加
if(j>=0) tmp+=(t[j--]-'0');
ret+=(tmp%10+'0');//相加后取模,尾插
tmp/=10;//除以10,得到的商就是需要进位的数
}
reverse(ret.begin(),ret.end());//把字符串逆序
return ret;
}
};
链表的高精度加法
链表相加(二)_牛客题霸_牛客网 (nowcoder.com)https://www.nowcoder.com/practice/c56f6c70fb3f4849bc56e33ff2a50b6b?tpId=196&tqId=37147&ru=/exam/oj
和字符串的高精度加法一样,我们需要从后面开始访问链表,但是题目提供的链表是单链表,不是双向循环链表,不支持从后向前遍历链表,所以需要自己写一个函数,翻转链表。
翻转链表(带虚拟头节点)
在翻转链表时,我们定义一个虚拟头节点,方便头插。
1、没有虚拟头节点时,我们需要判断链表是否为空,不为空则直接头插,为空则需要特殊处理
2、设置了虚拟头节点之后,无论链表是否为空,不需要特殊处理,直接插入到虚拟头节点的后面即可(图中 0 为虚拟头节点)
翻转函数后得到的链表带虚拟头节点,在返回时,不需要返回虚拟头节点(不可以返回不属于原链表的结点),所以需要把虚拟头节点删除。
链表翻转后的高精度加法和字符串的高精度加法原理相同。
class Solution {
public:
ListNode* reverse(ListNode* head)
{
ListNode* newhead=new ListNode(0);//虚拟头节点
ListNode* cur=head;//遍历head
while(cur)
{
ListNode*next=cur->next;
cur->next=newhead->next;
newhead->next=cur;
cur=next;
}
cur=newhead->next;//保存虚拟头节点的下一个结点
delete newhead;//删除虚拟头节点
newhead=cur;
return newhead;//返回翻转后的链表
}
ListNode* addInList(ListNode* head1, ListNode* head2) {
head1=reverse(head1);
head2=reverse(head2);
ListNode*cur1=head1;
ListNode*cur2=head2;
ListNode* ret=new ListNode(0);//虚拟头节点
ListNode* end=ret;
int tmp=0;
while(cur1 || cur2 ||tmp)
{
if(cur1)
{
tmp+=cur1->val;
cur1=cur1->next;
}
if(cur2)
{
tmp+=cur2->val;
cur2=cur2->next;
}
ListNode* t=new ListNode(tmp%10);
end->next=t;
end=end->next;
tmp/=10;
}
cur1=ret->next;
delete ret;//删除虚拟头节点
ret=cur1;
ret = reverse(ret);//把ret翻转,得到最终结果
return ret;
}
};
字符串的高精度乘法
大数乘法_牛客题霸_牛客网 (nowcoder.com)https://www.nowcoder.com/practice/c4c488d4d40d4c4e9824c3650f7d5571?tpId=196&tqId=37177&ru=/exam/oj
关于高精度乘法,有一个很巧妙的方法, 同样也是模拟乘法:
假设现在需要计算 1234 * 24 的积,原本 1234 对应的下标从左往右对应从 1 到 4,翻转后,得到的字符串下标从左往右对应从 4 到 1,24同理,原本 1234 和 24 中 ' 4 ' 的下标是不对齐的,翻转后对齐了,之后我们可以进行相乘。
下标为 i 的数 和下标为 j 的数相乘,相乘的结果放在下标为 i+j 的位置,例如下标为 2 的数 2 和下标为 1 的数 2,相乘的结果 4 应该放在下标为 3 的位置,所有数相乘之后,再把对应位置的数相加并进位,翻转结果字符串即可,如图所示:
但是我们需要处理前导 0 的情况,比如 1000 * 0,得到的字符串为 0000,但我们只需要结果 0,所以我们当字符串的长度大于 1 且字符串的末尾为 0 时,需要尾删 0.
class Solution {
public:
string solve(string s, string t) {
reverse(s.begin(),s.end());//翻转字符串
reverse(t.begin(),t.end());
int m=s.size(),n=t.size();
vector<int> v(m+n);
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
v[i+j]+=(s[i]-'0')*(t[j]-'0');
}
}
int tmp=0;
string ret;
for(int i=0;i<v.size();i++)
{
tmp+=v[i];
ret+=(tmp%10+'0');
tmp/=10;
}
while(ret.size()>1 && ret.back()=='0') ret.pop_back();//处理前导0
reverse(ret.begin(),ret.end());
return ret;
}
};