掘金AI刷题第55题困难题解析|豆包MarsCode AI刷题

65 阅读8分钟

二进制大数求和与十进制转换详解

问题描述

小U和小R对二进制数字充满了好奇。他们希望找到一种方法,可以将两个可能非常长的二进制字符串相加,并以十进制的形式呈现。由于二进制字符串可能非常长,常规的方法(如直接转换为整数类型进行计算)可能会导致数据溢出或性能问题。因此,需要设计一个时间复杂度不超过 O(n²) 的算法,来完成这个任务。

示例:

  • 样例1:

    • 输入: binary1 = "101", binary2 = "110"
    • 输出: '11'
  • 样例2:

    • 输入: binary1 = "111111", binary2 = "10100"
    • 输出: '83'
  • 样例3:

    • 输入: binary1 = "111010101001001011", binary2 = "100010101001"
    • 输出: '242420'

问题分析

挑战与限制

  1. 大数处理: 由于二进制字符串可能非常长,直接将其转换为整数类型(如 intlong)会导致数据溢出。
  2. 时间复杂度要求: 需要在时间复杂度 O(n²) 内完成计算,其中 n 是二进制字符串的长度。

解决思路概述

  1. 二进制字符串相加: 首先,需要实现两个二进制字符串的加法,得到相加后的二进制结果。
  2. 二进制转十进制: 然后,将相加后的二进制字符串转换为十进制字符串。
  3. 大数运算: 由于无法使用内置的数据类型来处理超大的数字,需要使用字符串来模拟大数的加法和乘法。

详细解答

步骤一:实现二进制字符串的加法

思路:

  • 从二进制字符串的末尾开始逐位相加,模拟手算的过程。
  • 使用变量 carry 来记录进位。
  • 将结果存储在一个可变字符串(如 StringBuilder)中。

示意图:

  binary1:    1   0   1
                     
               i

  binary2:    1   1   0
                     
               j

  carry: 0

  sum = binary1[i] + binary2[j] + carry

实现:

public static String addBinaryStrings(String a, String b) {
    StringBuilder result = new StringBuilder();

    int i = a.length() - 1; // 指针 i 指向字符串 a 的末尾
    int j = b.length() - 1; // 指针 j 指向字符串 b 的末尾
    int carry = 0; // 进位

    while (i >= 0 || j >= 0 || carry > 0) {
        int sum = carry; // 当前位的和

        if (i >= 0) {
            sum += a.charAt(i) - '0';
            i--;
        }

        if (j >= 0) {
            sum += b.charAt(j) - '0';
            j--;
        }

        result.append(sum % 2); // 当前位的结果
        carry = sum / 2; // 更新进位
    }

    return result.reverse().toString(); // 反转字符串并返回
}

代码解释:

  • ij 指针: 从后往前遍历两个字符串。
  • carry 变量: 保存进位,可能为 0 或 1。
  • sum 变量: 当前位的总和,包括两个二进制位和进位。
  • 结果构建: 使用 StringBuilder 来存储结果,最后反转得到正确的顺序。

步骤二:将二进制字符串转换为十进制字符串

思路:

  • 模拟二进制转十进制的过程,从高位到低位遍历二进制字符串。
  • 初始十进制结果为 '0',每次遍历到新的二进制位,将当前十进制结果乘以 2,再加上当前位的值。

示意图:

对于二进制字符串 "1101",转换过程如下:

  1. 初始化:decimal = '0'

  2. 遍历第一个字符 '1'

    • decimal = multiplyByTwo(decimal) => '0' * 2 = '0'
    • decimal = addStrings(decimal, '1') => '0' + '1' = '1'
  3. 遍历第二个字符 '1'

    • decimal = multiplyByTwo(decimal) => '1' * 2 = '2'
    • decimal = addStrings(decimal, '1') => '2' + '1' = '3'
  4. 遍历第三个字符 '0'

    • decimal = multiplyByTwo(decimal) => '3' * 2 = '6'
    • 当前位为 '0',不需要加 1
  5. 遍历第四个字符 '1'

    • decimal = multiplyByTwo(decimal) => '6' * 2 = '12'
    • decimal = addStrings(decimal, '1') => '12' + '1' = '13'

实现:

public static String binaryToDecimal(String binary) {
    String decimal = "0"; // 初始化十进制结果为 "0"

    for (int i = 0; i < binary.length(); i++) {
        // 将十进制结果乘以 2
        decimal = multiplyByTwo(decimal);

        // 如果当前位为 '1',则加 1
        if (binary.charAt(i) == '1') {
            decimal = addStrings(decimal, "1");
        }
    }

    return decimal;
}

步骤三:实现大数的字符串加法和乘法

字符串加法 addStrings

思路:

  • 从低位到高位逐位相加,处理进位。
  • 使用 StringBuilder 存储结果,最后反转。

实现:

public static String addStrings(String num1, String num2) {
    StringBuilder result = new StringBuilder();

    int i = num1.length() - 1; // 指针 i 指向 num1 的末尾
    int j = num2.length() - 1; // 指针 j 指向 num2 的末尾
    int carry = 0; // 进位

    while (i >= 0 || j >= 0 || carry > 0) {
        int sum = carry; // 当前位的和

        if (i >= 0) {
            sum += num1.charAt(i) - '0';
            i--;
        }

        if (j >= 0) {
            sum += num2.charAt(j) - '0';
            j--;
        }

        result.append(sum % 10); // 当前位的结果
        carry = sum / 10; // 更新进位
    }

    return result.reverse().toString(); // 反转字符串并返回
}
字符串乘以 2 multiplyByTwo

思路:

  • 从低位到高位逐位相乘,处理进位。
  • 因为乘以 2,所以每一位的计算是当前数字乘以 2 加上进位。

实现:

public static String multiplyByTwo(String num) {
    StringBuilder result = new StringBuilder();

    int carry = 0; // 进位

    for (int i = num.length() - 1; i >= 0; i--) {
        int product = (num.charAt(i) - '0') * 2 + carry;
        result.append(product % 10);
        carry = product / 10;
    }

    if (carry > 0) {
        result.append(carry);
    }

    return result.reverse().toString(); // 反转字符串并返回
}

完整代码

public class Main {
    public static void main(String[] args) {
        // 测试样例
        String binary1 = "101";
        String binary2 = "110";
        System.out.println(solution(binary1, binary2)); // 输出:11

        binary1 = "111111";
        binary2 = "10100";
        System.out.println(solution(binary1, binary2)); // 输出:83

        binary1 = "111010101001001011";
        binary2 = "100010101001";
        System.out.println(solution(binary1, binary2)); // 输出:242420

        binary1 = "111010101001011";
        binary2 = "10010101001";
        System.out.println(solution(binary1, binary2)); // 输出:31220

        binary1 = "11";
        binary2 = "1";
        System.out.println(solution(binary1, binary2)); // 输出:4
    }

    public static String solution(String binary1, String binary2) {
        // 第一步:将两个二进制字符串相加
        String sumBinary = addBinaryStrings(binary1, binary2);

        // 第二步:将二进制字符串转换为十进制字符串
        String decimalResult = binaryToDecimal(sumBinary);

        return decimalResult;
    }

    // 实现二进制字符串的加法
    public static String addBinaryStrings(String a, String b) {
        StringBuilder result = new StringBuilder();

        int i = a.length() - 1; // 指针 i 指向字符串 a 的末尾
        int j = b.length() - 1; // 指针 j 指向字符串 b 的末尾
        int carry = 0; // 进位

        while (i >= 0 || j >= 0 || carry > 0) {
            int sum = carry; // 当前位的和

            if (i >= 0) {
                sum += a.charAt(i) - '0';
                i--;
            }

            if (j >= 0) {
                sum += b.charAt(j) - '0';
                j--;
            }

            result.append(sum % 2); // 当前位的结果
            carry = sum / 2; // 更新进位
        }

        return result.reverse().toString(); // 反转字符串并返回
    }

    // 将二进制字符串转换为十进制字符串
    public static String binaryToDecimal(String binary) {
        String decimal = "0"; // 初始化十进制结果为 "0"

        for (int i = 0; i < binary.length(); i++) {
            // 将十进制结果乘以 2
            decimal = multiplyByTwo(decimal);

            // 如果当前位为 '1',则加 1
            if (binary.charAt(i) == '1') {
                decimal = addStrings(decimal, "1");
            }
        }

        return decimal;
    }

    // 字符串表示的大整数加法
    public static String addStrings(String num1, String num2) {
        StringBuilder result = new StringBuilder();

        int i = num1.length() - 1; // 指针 i 指向 num1 的末尾
        int j = num2.length() - 1; // 指针 j 指向 num2 的末尾
        int carry = 0; // 进位

        while (i >= 0 || j >= 0 || carry > 0) {
            int sum = carry; // 当前位的和

            if (i >= 0) {
                sum += num1.charAt(i) - '0';
                i--;
            }

            if (j >= 0) {
                sum += num2.charAt(j) - '0';
                j--;
            }

            result.append(sum % 10); // 当前位的结果
            carry = sum / 10; // 更新进位
        }

        return result.reverse().toString(); // 反转字符串并返回
    }

    // 将字符串表示的大整数乘以 2
    public static String multiplyByTwo(String num) {
        StringBuilder result = new StringBuilder();

        int carry = 0; // 进位

        for (int i = num.length() - 1; i >= 0; i--) {
            int product = (num.charAt(i) - '0') * 2 + carry;
            result.append(product % 10);
            carry = product / 10;
        }

        if (carry > 0) {
            result.append(carry);
        }

        return result.reverse().toString(); // 反转字符串并返回
    }
}

代码详解

主函数 main

  • 测试了给定的样例,调用 solution 方法并输出结果。

方法 solution

  • 参数:

    • binary1:第一个二进制字符串。
    • binary2:第二个二进制字符串。
  • 返回值:

    • 相加后的二进制字符串转换为十进制字符串。
  • 逻辑:

    1. 调用 addBinaryStrings 方法,计算两个二进制字符串的和,得到 sumBinary
    2. 调用 binaryToDecimal 方法,将 sumBinary 转换为十进制字符串 decimalResult
    3. 返回 decimalResult

方法 addBinaryStrings

  • 功能:

    • 实现两个二进制字符串的加法。
  • 逻辑:

    • 使用指针 ij 从后往前遍历字符串 ab
    • 在循环中,计算当前位的和 sum,包括进位 carry
    • sum % 2 作为当前位的结果,carry = sum / 2 作为新的进位。
    • 最后,反转结果字符串并返回。

方法 binaryToDecimal

  • 功能:

    • 将二进制字符串转换为十进制字符串。
  • 逻辑:

    • 初始化十进制结果 decimal = "0"

    • 遍历二进制字符串的每一位:

      • 将当前十进制结果乘以 2,调用 multiplyByTwo 方法。
      • 如果当前位为 '1',则调用 addStrings 方法,加上 '1'

方法 addStrings

  • 功能:

    • 实现两个十进制字符串的加法。
  • 逻辑:

    • 使用指针 ij 从后往前遍历字符串 num1num2
    • 在循环中,计算当前位的和 sum,包括进位 carry
    • sum % 10 作为当前位的结果,carry = sum / 10 作为新的进位。
    • 最后,反转结果字符串并返回。

方法 multiplyByTwo

  • 功能:

    • 将十进制字符串表示的数字乘以 2。
  • 逻辑:

    • 从后往前遍历字符串 num
    • 对每一位,计算 product = 当前位数字 * 2 + carry
    • product % 10 作为当前位的结果,carry = product / 10 作为新的进位。
    • 最后,反转结果字符串并返回。

复杂度分析

  • 时间复杂度:

    • addBinaryStrings 方法: O(max(m, n)),其中 mn 是两个二进制字符串的长度。
    • binaryToDecimal 方法: 对于长度为 k 的二进制字符串,内部循环(乘法和加法)每次操作的时间复杂度为 O(L),其中 L 是当前十进制字符串的长度,最坏情况下 L = O(k)。因此,总的时间复杂度为 O(k²)。
    • 总体时间复杂度: O(n²),满足题目的要求。
  • 空间复杂度:

    • 主要是存储中间结果的字符串,空间复杂度为 O(n)。

注意: 该算法适用于任意长度的二进制字符串,但由于时间复杂度为 O(n²),对于非常长的字符串,计算时间可能会较长。在实际应用中,如果需要处理更长的字符串,可以考虑进一步优化算法,或者使用更高效的大数运算库。