Leetcode之415.字符串相加

题目

题目描述

给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。

提示

  • num1 和num2 的长度都小于 5100
  • num1 和num2 都只包含数字 0-9
  • num1 和num2 都不包含任何前导零
  • 你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式

来源

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/add-strings

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解

下面内容比较长,标题写了四种解法,实际上是同一种思路的四类代码写法,是对第一段代码的不断优化,最终的优化结果就等价于官方提供的代码解,所以下面的内容仅提供参考,会直接提供代码,具体思路已经写在代码中了。
本题同67. 二进制求和是一样的解题思路。

解法1

图片.png

class Solution {
    /**
     * <p>思路:单层for循环</p>
     * <p>步骤:</p>
     * <ul>
     *      <li>第一步,添加前导零,例如:"123"和"1"添加前导零是"123"和"001"</li>
     *      <li>第二步,局部变量</li>
     *      <li>第三步,循环遍历等长的字符串,计算每两位相加的和,并且处理进位问题</li>
     *      <li>第四步,收尾,最后仍然需要判断是否进位,即数字字符串的最高位是否会产生进位</li>
     * </ul>
     * <p>结果:成功</p>
     * <ul>
     *     <li>执行用时:5 ms, 在所有 Java 提交中击败了11.62% 的用户</li>
     *     <li>内存消耗:39.2 MB, 在所有 Java 提交中击败了5.07% 的用户</li>
     *     <li>通过测试用例:317 / 317</li>
     * </ul>
     *
     * @param num1 第一个数字字符串
     * @param num2 第二个数字字符串
     * @return 返回相加求和的结果字符串
     * @see {@link 第67题_二进制求和.解法2.Solution}
     */
    public String addStrings(String num1, String num2) {
        // 第一步,添加前导零,例如:"123"和"1"添加前导零是"123"和"001"
        if (num1.length() < num2.length()) {
            num1 = addLeadingZero(num1, num2.length() - num1.length());
        } else if (num1.length() > num2.length()) {
            num2 = addLeadingZero(num2, num1.length() - num2.length());
        }

        // 第二步,局部变量
        char[] chars1 = num1.toCharArray();
        char[] chars2 = num2.toCharArray();
        boolean isCarry = false;// 进位标志,判断两位数相加是否进位
        StringBuilder result = new StringBuilder();// 存放结果的字符串

        //
        for (int i = num1.length() - 1; i >= 0; i--) {
            // 将数字字符转换成整数int类型
            int n1 = Integer.parseInt(String.valueOf(chars1[i]));
            int n2 = Integer.parseInt(String.valueOf(chars2[i]));
            // 由于这里采用的是布尔值来作为进位标志,不能直接参与运算,必须进行判断
            // (n1+n2)%10是得到两个数(例如6+5=11)的余数1,由于可能存在进位所以要(n1+n2)%10+1
            // 但(n1+n2)%10+1又可能产生进位(例如:(8+1)%10+1=10),所以还需要再对10取余,即((n1 + n2) % 10 + 1) % 10
            result.insert(0, isCarry ? ((n1 + n2) % 10 + 1) % 10 : (n1 + n2) % 10);
            // 得到两位数相加的结果后,还需要判断是否会产生进位
            isCarry = isCarry ? (n1 + n2 + 1) >= 10 : (n1 + n2) >= 10;
        }

        // 第四步,收尾,最后仍然需要判断是否进位,即数字字符串的最高位是否会产生进位
        if (isCarry) {
            result.insert(0, '1');
        }
        return result.toString();
    }

    /**
     * <p>为指定字符串添加指定个数的前导零</p>
     *
     * @param num       指定字符串
     * @param zeroCount 指定个数的前导零
     * @return 添加前导零成功的字符串
     */
    private String addLeadingZero(String num, int zeroCount) {
        StringBuilder sb = new StringBuilder();
        for (int i = zeroCount; i > 0; i--) {
            sb.append('0');
        }
        sb.append(num);
        return sb.toString();
    }
}

解法2

解法1代码的优化。

class Solution {
    /**
     * <p>思路:解法1的优化。</p>
     * <p>优化:</p>
     * <ul>
     *     <li>优化一,将原来通过API来使数字字符转换成整数优化成数字字符减去'0'即可得到它所表示的具体数字。</li>
     *     <li>优化二,采用了整数(0和1)来表示是否产生进位,而不是布尔值(false和true),这样的好处是减少对布尔值的判断,让进位也参与到运算。</li>
     * </ul>
     * <p>步骤:</p>
     * <ul>
     *      <li>第一步,添加前导零,例如:"123"和"1"添加前导零是"123"和"001"</li>
     *      <li>第二步,局部变量</li>
     *      <li>第三步,循环遍历等长的字符串,计算每两位相加的和,并且处理进位问题</li>
     *      <li>第四步,收尾,最后仍然需要判断是否进位,即数字字符串的最高位是否会产生进位</li>
     * </ul>
     * <p>结果:成功</p>
     * <ul>
     *     <li>执行用时:3 ms, 在所有 Java 提交中击败了38.56% 的用户</li>
     *     <li>内存消耗:38.4 MB, 在所有 Java 提交中击败了65.73% 的用户</li>
     *     <li>通过测试用例:317 / 317</li>
     * </ul>
     *
     * @param num1 第一个数字字符串
     * @param num2 第二个数字字符串
     * @return 返回相加求和的结果字符串
     * @see {@link 第67题_二进制求和.解法2.Solution}
     */
    public String addStrings(String num1, String num2) {
        // 第一步,添加前导零,例如:"123"和"1"添加前导零是"123"和"001"
        if (num1.length() < num2.length()) {
            num1 = addLeadingZero(num1, num2.length() - num1.length());
        } else if (num1.length() > num2.length()) {
            num2 = addLeadingZero(num2, num1.length() - num2.length());
        }

        // 第二步,局部变量
        char[] chars1 = num1.toCharArray();
        char[] chars2 = num2.toCharArray();
        int isCarry = 0;// 进位标志,判断两位数相加是否进位,0表示未进位,1表示有进位。用整数0和1来表示可以直接参与到进位运算种
        StringBuilder result = new StringBuilder();// 存放结果的字符串

        // 第三步,循环遍历等长的字符串,计算每两位相加的和,并且处理进位问题
        for (int i = num1.length() - 1; i >= 0; i--) {
            // 将数字字符转换成整数int类型
            int n1 = chars1[i] - '0';
            int n2 = chars2[i] - '0';
            // 计算当前两位数字相加的结果
            result.insert(0, (n1 + n2 + isCarry) % 10);
            // 判断是否会产生进位
            isCarry = (n1 + n2 + isCarry) / 10;
        }

        // 第四步,收尾,最后仍然需要判断是否进位,即数字字符串的最高位是否会产生进位
        if (isCarry == 1) {
            result.insert(0, '1');
        }
        return result.toString();
    }

    /**
     * <p>为指定字符串添加指定个数的前导零</p>
     *
     * @param num       指定字符串
     * @param zeroCount 指定个数的前导零
     * @return 添加前导零成功的字符串
     */
    private String addLeadingZero(String num, int zeroCount) {
        StringBuilder sb = new StringBuilder();
        for (int i = zeroCount; i > 0; i--) {
            sb.append('0');
        }
        sb.append(num);
        return sb.toString();
    }
}

解法3

解法2代码的优化。

public class Solution {

    /**
     * <p>思路:解法2的优化。</p>
     * <p>优化:</p>
     * <ul>
     *     <li>优化一,优化掉了对前导零的处理。</li>
     * </ul>
     * <p>步骤:</p>
     * <ul>
     *      <li>第一步,同时循环遍历两个字符串,计算相等长度的数字和,并处理两数相加的进位问题</li>
     *      <li>第二步,处理字符串剩余数字字符的情况</li>
     *      <li>第三步,收尾,处理还可能存在进位的情况,即如果存在进位则插入'1'</li>
     * </ul>
     * <p>结果:成功</p>
     * <ul>
     *     <li>执行用时:3 ms, 在所有 Java 提交中击败了38.56% 的用户</li>
     *     <li>内存消耗:38.4 MB, 在所有 Java 提交中击败了61.06% 的用户</li>
     *     <li>通过测试用例:317 / 317</li>
     * </ul>
     *
     * @param num1 第一个数字字符串
     * @param num2 第二个数字字符串
     * @return 返回相加求和的结果字符串
     * @see {@link 第67题_二进制求和.解法2.Solution}
     */
    public String addStrings(String num1, String num2) {
        int i = num1.length() - 1, j = num2.length() - 1;// 双指针,i指向行num1中的字符,j指向num2中的字符
        int carry = 0;// 进位标志
        StringBuilder result = new StringBuilder();// 存放结果的字符串

        // 第一步,同时循环遍历两个字符串,计算相等长度的数字和,并处理两数相加的进位问题
        while (i >= 0 && j >= 0) {
            // 得到当前字符所表示的十进制数字
            int n1 = num1.charAt(i) - '0';
            int n2 = num2.charAt(j) - '0';
            // 计算两数相加的结果和,让进位也参与运算
            result.insert(0, (n1 + n2 + carry) % 10);
            // 设定两数相加是否产生进位
            carry = (n1 + n2 + carry) / 10;
            // 继续遍历下一个数字字符
            i--;
            j--;
        }

        // 第二步,处理字符串剩余数字字符的情况
        // 可能两个字符串的长度不一致,那么一定会有一个字符串会剩余一些数字字符没有相加,可以同0相加并且再与进位相加得到结果,相当于间接处理了前导零
        // 当num1字符串中存在剩余字符的情况
        while (i >= 0) {
            int n1 = num1.charAt(i) - '0';
            // 其实这里n2可以相当于0
            result.insert(0, (n1 + carry) % 10);
            carry = (n1 + carry) / 10;
            i--;
        }
        // 当num2字符串中存在剩余字符的情况
        while (j >= 0) {
            int n2 = num2.charAt(j) - '0';
            result.insert(0, (n2 + carry) % 10);
            carry = (n2 + carry) / 10;
            j--;
        }

        // 第三步,收尾,处理还可能存在进位的情况,即如果存在进位则插入'1'
        if (carry != 0) {
            result.insert(0, carry);
        }
        return result.toString();
    }
}

解法4

解法3代码的优化。

public class Solution {
    /**
     * <p>思路:解法3的优化。</p>
     * <p>优化:</p>
     * <ul>
     *     <li>优化一,优化掉了多个while循环(即对两个数字位数不同情况的处理),使得最剩下一个while循环,简化代码。</li>
     * </ul>
     * <p>步骤:</p>
     * <ul>
     *      <li>第一步,同时循环遍历两个字符串,计算相等长度的数字和,并处理两数相加的进位问题</li>
     * </ul>
     * <p>结果:成功</p>
     * <ul>
     *     <li>执行用时:3 ms, 在所有 Java 提交中击败了38.56% 的用户</li>
     *     <li>内存消耗:38.4 MB, 在所有 Java 提交中击败了64.84% 的用户</li>
     *     <li>通过测试用例:317 / 317</li>
     * </ul>
     *
     * @param num1 第一个数字字符串
     * @param num2 第二个数字字符串
     * @return 返回相加求和的结果字符串
     * @see {@link 第67题_二进制求和.解法2.Solution}
     */
    public String addStrings(String num1, String num2) {
        int i = num1.length() - 1, j = num2.length() - 1;// 双指针,i指向行num1中的字符,j指向num2中的字符
        int carry = 0;// 进位标志
        StringBuilder result = new StringBuilder();// 存放结果的字符串

        // 通过下面这个while循环就可以处理掉当字符串还剩余字符的情况了
        while (i >= 0 || j >= 0 || carry != 0) {
            // 得到当前字符所表示的十进制数字,当下标i或j为负数时返回0就等价于对位数较短的数字进行了补零操作,这样就可以优化掉两个数字位数不同的代码
            int n1 = i >= 0 ? num1.charAt(i) - '0' : 0;
            int n2 = j >= 0 ? num2.charAt(j) - '0' : 0;
            // 计算两数相加的结果和,让进位也参与运算
            result.insert(0, (n1 + n2 + carry) % 10);
            // 设定两数相加是否产生进位
            carry = (n1 + n2 + carry) / 10;
            // 继续遍历下一个数字字符
            i--;
            j--;
        }

        return result.toString();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值