LeetCode 67. 二进制求和:详细题解+代码拆解

LeetCode 上一道非常经典的基础题------67. 二进制求和。这道题虽然难度是简单,但却是面试中常考的基础题型,核心考察对二进制运算规则的理解,以及字符串、进位的处理技巧,非常适合新手入门练习。

先来看下题目要求:给你两个二进制字符串 a 和 b ,以二进制字符串的形式返回它们的和。这里要注意,二进制字符串仅由 '0' 和 '1' 组成,而且可能存在长度不一致的情况(比如 a 是 "11",b 是 "101"),同时不能直接将二进制字符串转成数字再相加(因为当字符串过长时,会超出数字类型的范围,导致溢出)。

先上最终 AC 代码,后面逐行拆解逻辑,保证新手也能看懂:

typescript 复制代码
function addBinary(a: string, b: string): string {
  // 步骤1:将两个二进制字符串反转,方便从低位到高位依次相加
  const aArr = a.split('').reverse().join('');
  const bArr = b.split('').reverse().join('');

  // 步骤2:确定循环的最大长度(取两个字符串长度的最大值)
  const n = Math.max(a.length, b.length);
  let carry: number = 0; // 进位标识,初始为0
  const ans = []; // 存储相加后的每一位结果

  // 步骤3:从低位到高位依次遍历,计算每一位的和
  for (let i = 0; i < n; i++) {
    // 取出当前位的数字,若超出字符串长度则视为0
    carry += (i < a.length ? parseInt(aArr[i]) : 0);
    carry += i < b.length ? parseInt(bArr[i]) : 0;
    // 当前位的结果 = (当前和)% 2(二进制逢2进1,余数就是当前位值)
    ans.push((carry % 2).toString());
    // 更新进位:取当前和的整数部分(逢2进1,商就是进位值)
    carry = Math.floor(carry / 2);
  }

  // 步骤4:循环结束后,若还有进位,需在最高位补1
  if (carry) {
    ans.push('1');
  }

  // 步骤5:将结果数组反转,拼接成字符串返回
  return ans.reverse().join('');
};

一、核心思路解析

二进制求和的核心规则和十进制求和类似,都是「从低位到高位依次相加,逢进制数进1」,区别在于十进制逢10进1,二进制逢2进1。

这道题的关键难点的是:字符串长度不一致 + 进位处理 + 避免数字溢出

我们的解决方案是:将字符串反转,从索引0开始(即原字符串的最低位)依次相加,用一个变量记录进位,遍历结束后判断是否还有剩余进位,最后将结果反转回来,就是最终的二进制和。

二、代码逐行拆解

1. 字符串反转处理

typescript 复制代码
const aArr = a.split('').reverse().join('');
const bArr = b.split('').reverse().join('');

这一步是解题的关键技巧。比如 a = "110"(二进制对应6),反转后就是 "011";b = "1011"(二进制对应11),反转后就是 "1101"。

为什么要反转?因为二进制求和需要从最低位开始计算,而字符串的索引是从左到右(对应二进制的高位到低位),反转后,索引0就对应原字符串的最低位,方便我们用循环从左到右遍历,依次计算每一位的和。

2. 初始化变量

typescript 复制代码
const n = Math.max(a.length, b.length);
let carry: number = 0;
const ans = [];
  • n:取两个字符串长度的最大值,确保循环能覆盖所有位(比如 a 长度2,b 长度3,循环3次,就能遍历完所有位)。

  • carry:进位标识,初始值为0。比如某一位相加得2(1+1),则进位为1,当前位为0;若相加得3(1+1+1,包含上一位的进位),则进位为1,当前位为1。

  • ans:数组用于存储每一位计算后的结果,最后拼接成字符串返回,比直接字符串拼接效率更高。

3. 核心循环:逐位相加

typescript 复制代码
for (let i = 0; i < n; i++) {
  carry += (i < a.length ? parseInt(aArr[i]) : 0);
  carry += i < b.length ? parseInt(bArr[i]) : 0;
  ans.push((carry % 2).toString());
  carry = Math.floor(carry / 2);
}

这一段是整个代码的核心,我们逐行拆解循环内的逻辑:

  • carry += (i < a.length ? parseInt(aArr[i]) : 0):判断当前索引 i 是否在 a 反转后的字符串长度范围内,如果在,就将当前位的字符转成数字(0或1)加到 carry 上;如果不在(说明 a 的长度比 b 短,当前位没有数字),就加0。

  • carry += i < b.length ? parseInt(bArr[i]) : 0:和上一步同理,处理 b 反转后的当前位。

  • ans.push((carry % 2).toString()):计算当前位的结果。因为二进制逢2进1,所以当前位的值就是 carry 除以2的余数(比如 carry=2,余数0;carry=3,余数1;carry=1,余数1),转成字符串后存入 ans 数组。

  • carry = Math.floor(carry / 2):更新进位。carry 除以2的整数部分,就是下一位需要加的进位(比如 carry=2,整数部分1;carry=3,整数部分1;carry=1,整数部分0)。

举个例子帮助理解:假设 a="11"(反转后"11"),b="1"(反转后"1"),循环执行2次(n=2):

  • i=0:carry = 0 + 1(aArr[0]) + 1(bArr[0])=2 → 当前位 2%2=0,ans.push("0") → carry=2/2=1。

  • i=1:carry = 1 + 1(aArr[1]) + 0(bArr[1]不存在)=2 → 当前位 2%2=0,ans.push("0") → carry=2/2=1。

4. 处理剩余进位

typescript 复制代码
if (carry) {
  ans.push('1');
}

循环结束后,可能还存在进位(比如上面的例子,循环结束后 carry=1),这时候需要在结果的最高位补1。比如上面的例子,ans 此时是 ["0","0"],push("1") 后变成 ["0","0","1"]。

5. 结果反转并返回

typescript 复制代码
return ans.reverse().join('');

因为我们之前将字符串反转后计算,ans 数组中存储的是从低位到高位的结果,所以需要再反转一次,才能得到从高位到低位的正确二进制字符串。比如上面的例子,ans 反转后是 ["1","0","0"],join('') 后就是 "100",也就是 11 + 1 = 100(二进制),结果正确。

三、总结与优化思路

这道题的核心逻辑就是「模拟二进制手工求和」,通过反转字符串简化低位遍历,用变量记录进位,处理好边界情况(长度不一致、剩余进位),就能轻松AC。

关于优化:

  • 当前代码的时间复杂度是 O(n)(n为两个字符串长度的最大值),空间复杂度是 O(n)(存储结果的数组),已经是最优解,因为必须遍历所有位才能计算出结果。

  • 如果不想用反转字符串的方式,也可以用指针从两个字符串的末尾(低位)开始遍历,逻辑类似,只是需要处理指针越界的情况,代码会稍微繁琐一点。

相关推荐
还是大剑师兰特2 小时前
为什么要用 import.meta.glob 加载 SVG 图标库
开发语言·前端·javascript
渣渣xiong2 小时前
《从零开始:前端转型AI agent直到就业第三天》
前端·ai编程
qiuge6782 小时前
一网打尽react手写题(上)
前端·javascript·react.js
炽烈小老头2 小时前
【每天学习一点算法 2026/04/01】零钱兑换
学习·算法
Morwit2 小时前
【力扣hot100】 70. 爬楼梯
c++·算法·leetcode·职场和发展
rhythmcc2 小时前
【npm&pnpm】基本使用
前端·npm·node.js
天天向上10242 小时前
vue3 el-date-picker 需求是想既可以输入,也可以选择, 且开始时间不能大于结束时间, 当不符合条件时border变成红色
前端·javascript·vue.js
kyle~2 小时前
前端框架---React
前端·react.js·前端框架
xiaotao1312 小时前
11. v4 版本升级指南
前端·css·tailwind