【手撕代码】大数问题:大数相加和大数相乘问题 + Karatsuba 算法

目  录:

1、大数相加

2、大数相乘

3、Karatsuba 算法


大数问题,简而言之就是不可以使用 int 类型直接进行加减乘除的操作,需要将其转换为字符串后再进行操作。

1、大数相加

分析:将两个数字字符串转换成相同长度,短的数字高位补 0,主要的操作在于两个字符串对应位置相加后得到的结果是否需要进位的问题,最后还需要剔除结果数组中高位为 0 的数。

public class BisDataPlus {

    public static String getPlus(String num1, String num2){
        if(num1 == null || num1 == num1){
            return num1 == null ? num2 : num1;
        }

        // 两个大数相加,相加后的数的位数比这两个数较大的那个数的位数多1
        int len = num1.length() > num2.length() ? num1.length() + 1 : num2.length() + 1;
        // 存两个数相加后的结果
        int[] res = new int[len];

        // 反转字符串,便于从低位到高位相加和最高位的进位导致的进位问题
        // 其实可以直接使用StringBuilder,这里看下怎么实现字符串的反转
        num1 = reverse(num1);
        num2 = reverse(num2);

        // 让两个字符串位数相同,不足的在后面补0,这样才能个位+个位,十位+十位,百位+百位+
        if(num1.length() < num2.length()){
            for(int i = 0; i < num2.length(); i++){
                num1 += "0";
            }
        }else{
            for(int i = 0; i < num1.length(); i++){
                num2 += "0";
            }
        }

        // 字符转int 如将char类型的'9'转成int类型的9,只需 (int)'9' - (int)'0'
        for(int j = 0; j < num1.length(); j++){
            res[j] = (int)num1.charAt(j) - (int)'0' + (int)num2.charAt(j) - (int)'0';
        }

        // 处理进位问题
        for(int k = 0; k < res.length; k++){
            // 判断是否需要进位
            if(res[k] / 10 > 0){
                // 向高位进位
                res[k + 1] += res[k] / 10;
            }
            // 该位置的数为res[k]模10的结果,即为进位后的结果
            res[k] = res[k] % 10;
        }

        // 处理高位可能多个0的问题
        int m = 0;
        for(int i = len - 1; i >= 0; i--){
            // 结果较高位数可能有多个0,不需要遍历应去除
            if(res[i] > 0){
                m = i;
                break;
            }
        }

        StringBuilder sb = new StringBuilder();
        // 从结果不为0的高位开始遍历
        for(int i = m; i >= 0; i--){
            sb.append(res[i]);
        }
        return sb.toString();
    }

    public static String reverse(String str){
        if(str == null || str.length() == 1){
            return str;
        }
        // 递归实现:下标为1的最后返回,实现了反转
        return reverse(str.substring(1) + str.charAt(0));
    }
}

2、大数相乘

  • 参考:https://www.jianshu.com/p/d5b1716a517a

假设有两个数:A = 863、B = 974。

那么乘积的个位不用说为 3 x 4 的个位得来,即12 % 10 = 2,向高位进1;

十位数:先看看将两个数的十位相乘 7 x 6 = 42,后面再加两个 0,即 4200,这样不可能得出一个数的乘积的十位,一个数的十位一定是一个数的十位和另外一个数的个位的乘积:6 x 4 + 7 x 3 = 45,加个 0 为 450,即十位是 5,百位进 4。

将输入转为数组、反序后:

【手撕代码】大数问题:大数相加和大数相乘问题 + Karatsuba 算法_第1张图片

就有 C[i + j] += A[i] * B[j] ,这里的下标运用的很巧妙。相当于 10^n,从而来表示数字的位数。

public class BigDataPlus {

    public static void getMultiple(String num1, String num2){

        StringBuilder s1 = new StringBuilder(num1);
        StringBuilder s2 = new StringBuilder(num2);
        // 将低位放在前面,这样更便于操作
        s1.reverse();
        s2.reverse();

        // res存储运算的结果,num1长的数 * num2长的数,结果不会超过num1+num2长
        int[] res = new int[s1.length() + s2.length()];

        for(int i = 0; i < s1.length(); i++){
            for(int j = 0; j < s2.length(); j++){
                int a = Integer.parseInt(String.valueOf(s1.charAt(i)));
                int b = Integer.parseInt(String.valueOf(s2.charAt(j)));
                res[i + j] += a * b;
            }
        }

        // 高位处理
        int k = 0;
        for(; k < res.length; k++){
            // 进位
            int carry = res[k] / 10;
            // 当前位的值
            res[k] = res[k] % 10;
            if(carry > 0){
                // 如果当前位进位了,则累加到高位
                res[k + 1] += carry;
            }
        }

        // 找到最高位
        for(k = res.length - 1; k >=0; k--){
            // 高位可能有0存在
            if(res[k] > 0){
                break;
            }
        }

        // 打印结果:由高位开始
        for(int n = 0; n <= k; n++){
            System.out.print(res[k - n]);
        }
    }
}
  • 在牛客网上看到了另外一种更加精简的代码版本,但其实就是上面一样的思路,只不过更加精简。
public class BigDataPlus {

    public static void getMultiple(String num1, String num2){
        if(num1 == null || num2 == null){
            return;
        }

        // res存储运算的结果,num1长的数 * num2长的数,结果不会超过num1+num2长
        int[] res = new int[num1.length() + num2.length()];
        for(int i = num1.length() -  1; i >= 0; i--){
            int x = num1.charAt(i) - '0';
            for(int j = num2.length() - 1; j >= 0; j--){
                int y = num2.charAt(j) - '0';
                // 高位(左边)的数等于(对应位置数的乘积+低位(右边)的进位)
                res[i + j] += (res[i + j + 1] + x * y) / 10;
                // 低位值为模10的值
                res[i + j + 1] = (res[i + j + 1] + x * y) % 10;
            }
        }

        String s = "";
        for(int i = 0; i < res.length; i++){
            // res的首位可能为0
            if(i == 0 && res[i] == 0){
                continue;
            }
            s += res[i];
        }
        System.out.println(s);
    }
}

3、Karatsuba 算法

参考:https://blog.csdn.net/u010983881/article/details/77503519

模拟普通乘法的计算方式,时间复杂度都是 O(n^2),而这个 Karatsuba 算法,时间复杂度仅有

public class Karatsuba {

    public static long karatsuba(long num1, long num2){
        // 递归终止条件
        if(num1 < 10 || num2 < 10){
            return num1 * num2;
        }

        // 计算拆分长度
        int size1 = String.valueOf(num1).length();
        int size2 = String.valueOf(num2).length();
        int halfN = Math.max(size1, size2) / 2;

        // 拆分为 a b c d
        long a = Long.valueOf(String.valueOf(num1).substring(0, size1 - halfN));
        long b = Long.valueOf(String.valueOf(num1).substring(size1 - halfN));
        long c = Long.valueOf(String.valueOf(num2).substring(0, size2 - halfN));
        long d = Long.valueOf(String.valueOf(num2).substring(size2 - halfN));

        // 计算 z2 z0 z1 此处的乘法使用递归
        long z2 = karatsuba(a, c);
        long z0 = karatsuba(b, d);
        long z1 = karatsuba((a + b), (c + d)) - z0 - z2;

        return (long)(z2 * Math.pow(10, (2 ^ halfN)) + z1 * Math.pow(10, halfN) + z0);
    }

    public static void main(String[] args) {
        System.out.println(karatsuba(721065475484731236L, 982161082972751393L));
    }
}

你可能感兴趣的:(手撕代码)