题目描述:
给定两个字符串形式的非负整数 num1
和num2
,计算它们的和并同样以字符串形式返回。
你不能使用任何內建的用于处理大整数的库(比如 BigInteger
),也不能直接将输入的字符串转换为整数形式。
示例1:
输入:num1 = "11", num2 = "123"
输出:"134"
示例 2:
输入:num1 = "456", num2 = "77"
输出:"533"
示例 3:
输入:num1 = "0", num2 = "0"
输出:"0"
提示:
1 <= num1.length, num2.length <= 104
num1
和num2
都只包含数字 0-9
num1
和num2
都不包含任何前导零我们先假设给定的不是字符串形式的数字,而是正常的非负整数,那么两数相加就遵循正常的加法运算,即个位数与个位数相加,十位数与十位数相加,依此类推。如果该位计算结果大于9,则向前进位。那么对于字符串形式的数字,我们就需要先将字符数字转换为数值数字,而在转换之前,我们首先要做到能对字符串进行遍历,遍历字符串的方式有很多,这里我们选择比较简单的下标+方括号
的形式来进行访问。由于数字相加是由末尾开始,所以在这里我们也一样先拿到字符串最后一个元素的下标:
int end1 = num1.size() - 1;
int end2 = num2.size() - 1;
拿到最后一个元素的下标后,就可以开始进行运算了。而在运算前,我们需要先将字符数字转换为数值数字。除此之外,在转换前,我们还需要先判断下标是否为0
,如果为0
则说明该字符串已经遍历完了,直接置零即可。所以我们可以这样写:
int val1 = 0;//默认给0
if (end1 >= 0)//如果字符串还没有遍历完,则进行转换
val1 = num1[end1] - '0';
int val2 = 0;
if (end2 >= 0)
val2 = num2[end2] - '0';
如果觉得这个地方写得有点啰嗦,那么我们也可以用三目运算符进行修改:
int val1 = end1 >= 0 ? num1[end1] - '0' : 0;
int val2 = end2 >= 0 ? num2[end2] - '0' : 0;
得到当前字符所代表的数值后,下面我们就可以对两个值进行相加。相加的时候,需要注意的是上一位的进位也要加进来,我们可以将进位初始化为0
,这样末位相加的时候虽然没有进位但也可以用同一个表达式计算:
int next = 0;//进位
int ret = val1 + val2 + next;
加完之后,我们就需要对ret
进行判断,如果ret
大于9
,则说明需要进位,注意这里不要忘记对ret
小于9
的情况进行处理,因为如果ret
小于9
的话next
就需要置零,如果这里不进行处理,那么之前的进位就会一直得到保留并继续和高位相加导致运算结果出错:
if (ret > 9)//ret大于9的话next进位
{
ret -= 10;
next = 1;
}
else//否则next置零
{
next = 0;
}
实际上,这个地方我们也可以进行简化:
next = ret / 10;//超过10的值除就是1,进位
ret = ret % 10;//模10留下余数
运算结束后,我们就可以将得到的值放回一个新的字符串中作为结果。这里需要注意的是,由于我们是从末位开始计算,因此每得到一位的值应该插入在上一个值的前面:
string strret;
strret.insert(0, 1, '0' + ret);//表示在字符串strret的首位插入1个'0' + ret所表示的字符
到这里,我们一位相加的过程就算结束了,继续往下进应该是高一位相加,直到字符串遍历完毕,因此,刚才的代码我们可以放在一个循环中:
while (end1 >= 0 || end2 >= 0)//end1和end2只要有一个不为0则说明字符串还没有遍历完,继续循环
{
int val1 = end1 >= 1 ? num1[end1] - '0' : 0;
int val2 = end2 >= 1 ? num2[end2] - '0' : 0;
int ret = val1 + val2 + next;
next = ret / 10;
ret = ret % 10;
strret.insert(0, 1, '0' + ret);
strret += ('0' + ret);
--end1;
--end2;
}
当循环结束时,我们可能会觉得这个时候strret
中存放的字符串代表的就是相加后的值了,但是实际上,如果刚才的循环中最高位相加还产生了进位,由于此时字符串已经遍历到最高位,循环将不再进行,所以出了循环之后我们还需要再进行一次判断,如果有进位,就需要把进位插在首字符的位置:
if (next == 1)
strret.insert(0, 1, '1');
return strret;
string addStrings(string num1, string num2)
{
int end1 = num1.size() - 1;
int end2 = num2.size() - 1;
int next = 0;
string strret;
while (end1 >= 0 || end2 >= 0)
{
int val1 = end1 >= 0 ? num1[end1] - '0' : 0;
int val2 = end2 >= 0 ? num2[end2] - '0' : 0;
int ret = val1 + val2 + next;
next = ret / 10;
ret = ret % 10;
strret.insert(0, 1, '0' + ret);
--end1;
--end2;
}
if (next == 1)
strret.insert(0, 1, '1');
return strret;
}
运行结果:
从运行结果可以看到,当前我们的代码效率还是比较低的,那么有哪些地方可以优化呢?
在刚才的代码中,最影响效率的其实是下面这段代码:
strret.insert(0, 1, '0' + ret);
其原因在于,每插入一个字符,都要将原来的字符向后移动一位,也就是说插入第一个值时会挪一下,插入第二个值会挪两下,依此类推,插入第n个值时会挪n下,可以看到挪动的次数会以一个等差数列的形式呈现,而由等差数列的求和公式可知这是一个n2的数量级,也就是说它的时间复杂度为n2。
而如果我们采用尾部插入的形式,每次插入就不需要对原来的数据进行移动,只需要在插入完成后将字符串倒置一下即可;除此之外,由于每次插入都需要扩容,所以为了进一步提高效率,我们可以事先把需要的空间开辟出来,而这个空间刚好就是长的字符串加1
。那么根据前面的分析,尾插我们可以用+=
实现,而在标准库函数中,可以实现逆置的函数为reverse
,string
类中有用来开辟空间的reserve
,那么我们就可以对代码进行如下修改:
string addStrings(string num1, string num2)
{
int end1 = num1.size() - 1;
int end2 = num2.size() - 1;
int next = 0;
string strret;
strret.reserve(num1.size() > num2.size() ? num1.size() + 1 : num2.size() + 1);//提前开辟空间
while (end1 >= 0 || end2 >= 0)
{
int val1 = end1 >= 0 ? num1[end1] - '0' : 0;
int val2 = end2 >= 0 ? num2[end2] - '0' : 0;
int ret = val1 + val2 + next;
next = ret / 10;
ret = ret % 10;
strret += ('0' + ret);//尾插
--end1;
--end2;
}
if (next == 1)
strret += '1';
reverse(strret.begin(), strret.end());//倒置
return strret;
}
运行结果:
可以看到,优化过后的代码运行起来效率就很高了。