LeetCode算法日记 - Day 34: 二进制求和、字符串相乘

目录

[1. 二进制求和](#1. 二进制求和)

[1.1 题目解析](#1.1 题目解析)

[1.2 解法](#1.2 解法)

[1.3 代码实现](#1.3 代码实现)

[2. 字符串相乘](#2. 字符串相乘)

[2.1 题目解析](#2.1 题目解析)

[2.2 解法](#2.2 解法)

[2.3 代码实现](#2.3 代码实现)


1. 二进制求和

67. 二进制求和 - 力扣(LeetCode)

给你两个二进制字符串 ab ,以二进制字符串的形式返回它们的和。

示例 1:

复制代码
输入:a = "11", b = "1"
输出:"100"

示例 2:

复制代码
输入:a = "1010", b = "1011"
输出:"10101"

提示:

  • 1 <= a.length, b.length <= 104
  • ab 仅由字符 '0''1' 组成
  • 字符串如果不是 "0" ,就不含前导零

1.1 题目解析

题目本质:

把两个仅含 0/1 的长字符串当成二进制大整数,相加并以二进制字符串返回。本质就是"逐位相加 + 维护进位"。

常规解法:

把二进制转成整型/长整型相加,再转回二进制。

问题分析:

长度可达 10^4,超出整型范围;而且频繁进制转换不必要、易溢出。应当直接在字符串层面做"竖式加法"。预计最佳复杂度:一次线性扫描 O(n)

思路转折:

要想高效且安全 → 不做数值转换 → 双指针从右到左 逐位相加,sum = carry + bitA + bitB,当前位写 sum % 2,新的进位 sum / 2

这样无需补零,只要在循环里判断指针是否越界即可;最后把构建的结果反转即可得到正确顺序。

1.2 解法

**算法思想:**设 i = a.length()-1、j = b.length()-1、carry = 0。循环条件:i>=0 || j>=0 || carry!=0。 每轮:

  • sum = carry + (i>=0 ? a[i]-'0' : 0) + (j>=0 ? b[j]-'0' : 0)

  • 结果位:sum % 2

  • 新进位:sum / 2

**i)**置双指针到两串末尾,carry=0,准备 StringBuilder(可预分配容量 maxLen+1)。

**ii)**循环:只要任一串未遍历完或还有进位,就继续:

  • 读各自当前位(越界当 0);

  • 相加得 sum;

  • append(sum % 2) 到 sb(此时是低位在前);

  • carry = sum / 2;指针左移。

**iii)**循环结束后 sb.reverse() 得到最终结果。

易错点:

  • while 条件必须是 carry != 0,不能写成 carry >= 0,否则可能死循环。

  • charAt(i) 是字符,要先减 '0' 转为数值位。

  • 右到左加;别从左往右。

  • 注意不等长字符串:用 if (i>=0)/if (j>=0) 判断是否还能取位。

  • StringBuilder.append(int) 对 0/1是安全的;也可写 append((char)('0' + (sum & 1)))。

  • 别在循环里频繁字符串拼接 +,会产生大量中间对象,影响性能。

1.3 代码实现

java 复制代码
class Solution {
    public String addBinary(String a, String b) {
        int i = a.length() - 1, j = b.length() - 1;
        int carry = 0;
        // 预估容量:最大长度 + 1(可能多出一个进位)
        StringBuilder sb = new StringBuilder(Math.max(a.length(), b.length()) + 1);

        while (i >= 0 || j >= 0 || carry != 0) {
            int sum = carry;
            if (i >= 0) sum += a.charAt(i--) - '0';
            if (j >= 0) sum += b.charAt(j--) - '0';

            sb.append(sum % 2);   // 当前位
            carry = sum / 2;      // 新进位
        }

        return sb.reverse().toString();
    }
}

复杂度分析:

  • 时间:O(n),其中 n = max(|a|, |b|),每位最多访问一次。

  • 空间:O(n),StringBuilder 存放结果(最坏多一位进位)。

2. 字符串相乘

43. 字符串相乘 - 力扣(LeetCode)

给定两个以字符串形式表示的非负整数 num1num2,返回 num1num2 的乘积,它们的乘积也表示为字符串形式。

**注意:**不能使用任何内置的 BigInteger 库或直接将输入转换为整数。

示例 1:

复制代码
输入: num1 = "2", num2 = "3"
输出: "6"

示例 2:

复制代码
输入: num1 = "123", num2 = "456"
输出: "56088"

提示:

  • 1 <= num1.length, num2.length <= 200
  • num1num2 只能由数字组成。
  • num1num2 都不包含任何前导零,除了数字0本身。

2.1 题目解析

题目本质:

把两个十进制数字串当作"大整数"做乘法,返回其十进制字符串。可抽象为"竖式乘法在字符串层面的模拟"。

常规解法:

直接把 num1/num2 转为内置整数或 BigInteger 再相乘,最后转回字符串。

问题分析:

数据长度可达 200,超出 64 位整数范围;题目也禁止 用大整数库或直接转整型。必须在字符/数组层面完成乘法。理想复杂度为 O(mn) (m、n 为两字符串长度),空间 O(m+n)

思路转折:

要想安全且高效 → 放弃内置数值运算 → 用"竖式乘法":

  • 两两相乘,按位权落在正确的"结果槽位"上;

  • 把所有乘积先累加到对应槽位(相当于"卷积");

  • 最后统一处理进位 ,再生成字符串。

    这样实现清晰、易于调试,也能稳定做到 O(mn)。

2.2 解法

算法思想: 算法思想:

令 a = reverse(num1)、b = reverse(num2)(把最低位放前面),准备长度为 m+n-1 的整型数组 tmp 用于"无进位卷积":

mp[i+j]+=(a[i]−′0′)⋅(b[j]−′0′)

然后从低到高扫描 tmp:

sum←carry+tmp[cur],输出(sum%10),carry←⌊sum/10⌋

若扫描完仍有 carry,继续"吐出"高位。最后反转得到正常顺序,并去掉可能出现的前导零。

i) 特判:若任一输入为 "0",直接返回 "0"

**ii)**反转两个字符串并转为 char[]。

**iii)**申请int[] tmp(长度 m+n-1),双重循环累加 tmp[i+j] += (a[i]-'0')*(b[j]-'0')。

**iiii)**统一进位:用一个移动的 carry 从 tmp[0] 扫到 tmp[len-1],每步输出当前个位、更新进位;若 carry 仍非 0,继续输出直到为 0。

iiiii) 删除将来会变成前导零的尾部 0(因为此时字符串是"低位在前"),再反转得到答案。

易错点:

  • 卷积阶段必须是 +=,不能写成 =(会覆盖之前累加)。

  • 记得把字符转数字:ch - '0'。

  • 结果桶长度为 m+n-1(卷积长度);统一进位阶段会把最高位的进位继续"吐出来"(所以最终位数≤m+n)。

  • 统一进位的循环条件建议写成 while (cur < tmp.length || carry != 0);sum 初始化为 carry,再按需加 tmp[cur]。

  • 去"前导零"的时机:在反转之前删尾部 0(至少保留 1 位,避免把 0 本身清空)。

2.3 代码实现

java 复制代码
class Solution {
    public String multiply(String num1, String num2) {
        // 1) 边界:任一为 "0"
        if (num1.equals("0") || num2.equals("0")) return "0";

        int m = num1.length(), n = num2.length();
        char[] a = new StringBuilder(num1).reverse().toString().toCharArray();
        char[] b = new StringBuilder(num2).reverse().toString().toCharArray();

        // 2) 无进位卷积:长度 m + n - 1
        int[] tmp = new int[m + n - 1];
        for (int i = 0; i < m; i++) {
            int di = a[i] - '0';
            for (int j = 0; j < n; j++) {
                int dj = b[j] - '0';
                tmp[i + j] += di * dj; // 累加!!不是覆盖
            }
        }

        // 3) 统一进位:从低到高,把每格拆成 个位(输出) + 进位(带走)
        StringBuilder sb = new StringBuilder(m + n);
        int cur = 0, carry = 0;
        while (cur < tmp.length || carry != 0) {
            if (cur < tmp.length) carry += tmp[cur++];
            sb.append((char) ('0' + (carry % 10)));
            carry /= 10;
        }

        // 4) 去掉将来会成为"前导零"的尾部 0(至少保留1位)
        while (sb.length() > 1 && sb.charAt(sb.length() - 1) == '0') {
            sb.deleteCharAt(sb.length() - 1);
        }

        // 5) 反转得到正常顺序
        return sb.reverse().toString();
    }
}

复杂度分析:

  • 时间:O(mn),双重循环累积卷积项,统一进位线性扫描一次。

  • 空间:O(m+n),tmp 与输出字符串(最坏 m+n 位)。

相关推荐
哈泽尔都23 分钟前
运动控制教学——5分钟学会样条曲线算法!(三次样条曲线,B样条曲线)
c++·人工智能·算法·机器学习·matlab·贪心算法·机器人
阿波罗尼亚29 分钟前
复杂查询:直接查询/子查询/视图/CTE
java·前端·数据库
小镇学者38 分钟前
【NOI】在信奥赛中 什么是函数交互题?
算法
goTsHgo39 分钟前
Spring XML 配置简介
xml·java·spring
青柠编程41 分钟前
基于 Spring Boot 的医疗病历信息交互平台架构设计
java·spring boot·后端
未知陨落1 小时前
LeetCode:62.N皇后
算法·leetcode
专注代码七年1 小时前
IDEA JVM优化配置idea64.vmoptions - 保守兼容版本 兼容IDEA 2023.3.6版本【亲测可用】
java·jvm·intellij-idea
疯癫的老码农1 小时前
【word解析】Java文件解析问题排查:无法找到OMML2MML.xsl的IO异常解析
java·开发语言·spring boot·spring·maven
测试老哥2 小时前
软件测试之单元测试详解
自动化测试·软件测试·python·测试工具·职场和发展·单元测试·测试用例
myw0712052 小时前
Leetcode94.二叉数的中序遍历练习
c语言·数据结构·笔记·算法