漫画算法-小灰的算法之旅(26)
1. 如何实现大整数相加
Q:给出两个很大的整数,要求实现程序求出两个整数之和。注:整数可能超过long类型范围
方法(模拟竖式加加法)
思路
考虑到数字可能会超过基础数据类型,因此我们无法采用传统的计算方式来计算超大型数字。但我们学过「列竖时计算」,因此本题可以采用模拟【竖式加法】的计算过程,来实现问题求解。
实现步骤
第一步:创建两个整型数组,数组长度是较大整数的位数+1。然后把每一个整数倒叙存储到数组中,整数的个位存于数组下标为0的位置,最高位存于数组的尾部。之所以倒序存储,是因为这样更符合从左到右访问数组的习惯。
第二步:创建结果数组,结果数组的长度同样是较大整数的位数+1,+1的目的很明显,是给最高位仅为预留的。
第三步:遍历两个数组,从左到右按照对应下标把元素两两相加,就像「计算竖式」一样。在本示例中,最先想家的是数组A的第1个元素8和数组B的第1个元素9,结果是7,进位是1。把7填充到result数组的对应下标位置,进位的1填充到下一个位置。
第2组相加的是数组A的第2个元素1和数组B的第2个元素2,结果是3,再加上刚才的进位1,把4填充到result数组的对应下标位置。
第3组相加的是数组A的第3个元素3和数组B的第3个元素1,结果是4,把4填充到result数组的对应下标位置。
依次类推…一直把数组的所有元素都相加完毕为止。
第4步:把result数组的全部元素再次逆序,去掉首位的0,就是最终结果。
时间复杂度
该方法的创建数组、按位计算、结果逆序的时间复杂度均为O(n),因此整体的时间复杂度为O(n);空间复杂度,涉及到3个数组的创建,因此空间复杂度也为O(n)。
代码实现
/**
* 大整数求和
* param bigNumberA. 大整数A
* param bigNumberB 大整数B
*/
public static String bigNumberSum(String bigNumberA,String bigNumberB){
// 1. 把两个大整数用数组逆序存储,数组长度等于较大整数位数+1
int maxLength= bigNumberA.length()>bigNumberB.length() ? bigNumberA.length() : bigNumberB.length();
int[] arrayA =new int[maxLength+1];
int arrayB[] =new int[maxLength+1];
for(int i=0;i<bigNumberA.length();i++){
arrayA[i]=bigNumberA.charAt(bigNumberA.length()-1-i) -'0';
}
for(int i=0;i<bigNumberB.length();i++){
arrayB[i]=bigNumberB.charAt(bigNumberB.length()-1-i) -'0';
}
//2. 构建result数组,数组长度等于较大整数位数+1
int[] result=new int[maxLength+1];
//3. 遍历数组,按位相加
for(int i=0;i<result.length;i++){
int temp=result[i];
temp+=arrayA[i];
temp+=arrayB[i];
//判断是否进位
if(temp>=10){
temp=temp-10;
result[i+1]=1;
}
result[i]=temp;
}
// 4. 把result数组再次逆序并转成String
StringBuilder sb=new StringBuilder();
//是否找到大整数的最高有效位
boolean findFirst=false;
for(int i=result.length-1;i>=0;i--){
if(!findFirst){
if(result[i]==0){
continue;
}
findFirst=true;
}
sb.append(result[i]);
}
return sb.toString();
}
public static void main(String[] args){
System.out.println(bigNumberSum("426709752318","95481253129"));
}
golang实现
func compareLength(strA, strB string) int {
if len(strA) > len(strB) {
return len(strA)
}
return len(strB)
}
func bigNumberSum(bigNumberA, bigNumberB string) string {
//1. 把两个大整数用数组逆序存储,数组长度等于较大整数位数+1
maxLength := compareLength(bigNumberA, bigNumberB)
total := maxLength + 1
arrayA := make([]int, total)
arrayB := make([]int, total)
for i := 0; i < len(bigNumberA); i++ {
arrayA[i] = int(bigNumberA[len(bigNumberA)-1-i] - '0')
}
for i := 0; i < len(bigNumberB); i++ {
arrayB[i] = int(bigNumberB[len(bigNumberB)-1-i] - '0')
}
//2. 构造接收result数组,数组长度等于较大整数位数+1
result := make([]int, total)
//3. 遍历数组,按位相加
for i := 0; i < maxLength+1; i++ {
temp := result[i]
temp += arrayA[i]
temp += arrayB[i]
//判断是否进位
if temp >= 10 {
temp = temp - 10
result[i+1] = 1
}
result[i] = temp
}
//4. 把result 数组再次逆序并转成string
ans := ""
findFirst := false
for i := len(result) - 1; i >= 0; i-- {
if !findFirst {
if result[i] == 0 {
continue
}
findFirst = true
}
ans += strconv.Itoa(result[i])
}
if ans == "" {
return "0"
}
return ans
}
func main() {
strA :="426709752318"
strB :="95481253129"
sum := bigNumberSum(strA, strB)
log.Println(sum)
}
代码优化
依旧采用模拟「竖式计算」的方式求解,只不过我们可以不用新开辟内存空间来辅助计算,而是采用双指针的方式处理。具体步骤:
定义两个指针 i
和j
分别指向bigNumberA
和bigNumberB
的尾端,即最低位;并定义一个变量add
维护当前是否有进位,然后从末尾到开头依次相加即可。你可能会想两个数字位数不同该如何处理,这里我们统一在指针当前下标处于负数的时候返回0
,等价于对位较短的数字进行了补零操作,这样就可以除去两个数位不同情况的处理。
时间复杂度
改进后的时间复杂度为:O(max(len1,len2));即取决于「竖式加法」中加大数的位数
因为我们省去了辅助数组,并没有开辟新的空间,因此空间复杂度为O(1)。
golang实现
func bigNumberSumV2(bigNumberA, bigNumberB string) string {
// 定义辅助进位标识
add :=0
// 接收计算结果
ans :=""
for i,j :=len(bigNumberA)-1, len(bigNumberB)-1;i>=0 || j>=0 || add!=0; i,j=i-1,j-1 {
var x,y int
if i>=0 {
x=int(bigNumberA[i]-'0')
}
if j>=0 {
y=int(bigNumberB[j]-'0')
}
// 获取每次求和值
result :=x+y+add
// 将结果对10求余可巧妙解决进位的问题
ans=strconv.Itoa(result%10)+ans
// 将结果相除可初始化进位
add=result/10
}
return ans
}
func main() {
strA :="426709752318"
strB :="95481253129"
v2 := bigNumberSumV2(strA, strB)
log.Println(v2)
}