当前位置: 首页 > news >正文

【LeetCode】67. 二进制求和

文章目录

  • 67. 二进制求和
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 提示:
    • 解题思路
      • 问题深度分析
        • 问题本质
        • 核心思想
        • 典型情况分析
        • 算法对比
      • 算法流程图
        • 主算法流程(双指针+进位)
        • 二进制加法详细流程
        • 进位规则说明
      • 复杂度分析
        • 时间复杂度详解
        • 空间复杂度详解
      • 关键优化技巧
        • 技巧1:双指针+进位(最优解法)
        • 技巧2:直接构建结果(前插法)
        • 技巧3:递归实现
        • 技巧4:位运算优化
      • 边界情况处理
      • 测试用例设计
        • 基础测试
        • 相同长度
        • 不同长度
        • 边界情况
      • 常见错误与陷阱
        • 错误1:忘记处理最后的进位
        • 错误2:字符转数字错误
        • 错误3:索引越界
      • 实战技巧总结
      • 进阶扩展
        • 扩展1:十六进制加法
        • 扩展2:任意进制加法
        • 扩展3:二进制减法
      • 应用场景
    • 代码实现
    • 测试结果
    • 核心收获
    • 应用拓展
    • 完整题解代码

67. 二进制求和

题目描述

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

示例 1:

输入:a = “11”, b = “1”
输出:“100”

示例 2:

输入:a = “1010”, b = “1011”
输出:“10101”

提示:

  • 1 <= a.length, b.length <= 104
  • a 和 b 仅由字符 ‘0’ 或 ‘1’ 组成
  • 字符串如果不是 “0” ,就不含前导零

解题思路

问题深度分析

这是一道二进制字符串加法问题,核心在于模拟二进制加法的进位过程。虽然题目简单,但涉及到字符串处理进位逻辑不同长度字符串的处理,是理解二进制运算和字符串操作的经典题目。

问题本质

给定两个二进制字符串,需要计算它们的和并以二进制字符串形式返回。关键问题:

  • 二进制进位:1+1=10(二进制),需要进位
  • 长度不同:两个字符串长度可能不同
  • 从右向左:需要从低位开始计算
  • 进位传播:进位可能一直传播到最高位
核心思想

模拟二进制加法

  1. 双指针逆序:从两个字符串末尾开始,分别向前遍历
  2. 逐位相加:每次取两个字符串对应位的数字+进位
  3. 计算进位:sum / 2 得到进位,sum % 2 得到当前位
  4. 构建结果:将计算结果插入结果字符串开头
  5. 处理最后进位:如果最后还有进位,需要加到结果前面
典型情况分析

情况1:无进位

  11    (3)
+  1    (1)
----100    (4)

情况2:有进位

 1010   (10)
+1011   (11)
-----
10101   (21)

情况3:长度不同

  1111  (15)
+    1  (1)
------10000  (16)

情况4:全是1(最难)

 1111   (15)
+1111   (15)
-----
11110   (30)
算法对比
算法时间复杂度空间复杂度特点
双指针+进位O(max(m,n))O(1)最优解法,逻辑清晰
StringBuilderO(max(m,n))O(1)使用字符串构建器
递归实现O(max(m,n))O(n)递归栈空间,代码简洁
转换为整数O(max(m,n))O(1)仅适用于短字符串

注:m和n分别为两个字符串的长度,双指针+进位是最优解法

算法流程图

主算法流程(双指针+进位)
开始: 输入a, b
初始化i=a.len-1, j=b.len-1
初始化carry=0, result=空
i>=0 或 j>=0 或 carry>0?
返回result
计算sum=carry
i >= 0?
sum += a i - '0'
j >= 0?
i--
sum += b j - '0'
计算当前位
j--
result = sum%2 的字符 + result
carry = sum / 2
二进制加法详细流程
0
1
2
3
二进制加法开始
从两个字符串末尾开始
初始化carry=0
取a的当前位, 默认0
取b的当前位, 默认0
sum = a位 + b位 + carry
sum值?
当前位=0, carry=0
当前位=1, carry=0
当前位=0, carry=1
当前位=1, carry=1
结果前面插入当前位
还有位或有进位?
返回结果字符串
进位规则说明
二进制加法进位规则
0 + 0 + 0 = 0, carry=0
0 + 0 + 1 = 1, carry=0
0 + 1 + 0 = 1, carry=0
0 + 1 + 1 = 0, carry=1
1 + 0 + 0 = 1, carry=0
1 + 0 + 1 = 0, carry=1
1 + 1 + 0 = 0, carry=1
1 + 1 + 1 = 1, carry=1

复杂度分析

时间复杂度详解

双指针算法:O(max(m, n))

  • 需要遍历较长字符串的所有位
  • 每位的处理时间为O(1)
  • m为字符串a的长度,n为字符串b的长度

各操作复杂度

  • 字符串遍历:O(max(m, n))
  • 字符转数字:O(1)
  • 进位计算:O(1)
  • 字符串拼接:O(1)每次(使用StringBuilder)
空间复杂度详解

双指针算法:O(1)

  • 不考虑结果字符串,只使用常数额外空间
  • carry变量:O(1)
  • 索引变量:O(1)

实际空间:O(max(m, n))

  • 结果字符串长度最多为max(m, n) + 1
  • 使用StringBuilder可以优化拼接效率

关键优化技巧

技巧1:双指针+进位(最优解法)
// 双指针+进位解法
func addBinary(a string, b string) string {i, j := len(a)-1, len(b)-1carry := 0var result strings.Builderfor i >= 0 || j >= 0 || carry > 0 {sum := carryif i >= 0 {sum += int(a[i] - '0')i--}if j >= 0 {sum += int(b[j] - '0')j--}result.WriteByte(byte('0' + sum%2))carry = sum / 2}// 反转结果res := result.String()return reverse(res)
}func reverse(s string) string {runes := []rune(s)for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {runes[i], runes[j] = runes[j], runes[i]}return string(runes)
}

优势

  • 逻辑清晰
  • 时间O(max(m,n))
  • 空间O(1)(不计结果)
技巧2:直接构建结果(前插法)
// 直接前插构建结果
func addBinaryPrefix(a string, b string) string {i, j := len(a)-1, len(b)-1carry := 0result := ""for i >= 0 || j >= 0 || carry > 0 {sum := carryif i >= 0 {sum += int(a[i] - '0')i--}if j >= 0 {sum += int(b[j] - '0')j--}// 直接在前面插入result = string('0'+byte(sum%2)) + resultcarry = sum / 2}return result
}

注意:字符串前插每次O(n),总复杂度O(n²),不推荐

技巧3:递归实现
// 递归解法
func addBinaryRecursive(a string, b string) string {return addHelper(a, b, len(a)-1, len(b)-1, 0)
}func addHelper(a, b string, i, j, carry int) string {// 递归终止条件if i < 0 && j < 0 && carry == 0 {return ""}sum := carryif i >= 0 {sum += int(a[i] - '0')}if j >= 0 {sum += int(b[j] - '0')}// 递归处理前面的位prefix := addHelper(a, b, i-1, j-1, sum/2)return prefix + string('0'+byte(sum%2))
}

特点

  • 代码优雅
  • 递归栈O(max(m,n))
  • 字符串拼接可能有性能问题
技巧4:位运算优化
// 位运算解法(仅适用于短字符串)
func addBinaryBitwise(a string, b string) string {// 转换为整数(注意:仅适用于长度<=63的字符串)numA := binaryToInt(a)numB := binaryToInt(b)// 使用位运算实现加法for numB != 0 {sum := numA ^ numB  // 不带进位的和carry := (numA & numB) << 1  // 进位numA = sumnumB = carry}return intToBinary(numA)
}func binaryToInt(s string) int64 {var result int64for _, ch := range s {result = result*2 + int64(ch-'0')}return result
}func intToBinary(n int64) string {if n == 0 {return "0"}var result stringfor n > 0 {result = string('0'+byte(n%2)) + resultn /= 2}return result
}

注意:只适用于短字符串,长字符串会溢出

边界情况处理

  1. 长度不同"1111" + "1""10000"
  2. 一个为0"0" + "1010""1010"
  3. 都是0"0" + "0""0"
  4. 最后有进位"1" + "1""10"
  5. 超长字符串:需要处理10000位的情况

测试用例设计

基础测试
输入: a = "11", b = "1"
输出: "100"
说明: 3 + 1 = 4
相同长度
输入: a = "1010", b = "1011"
输出: "10101"
说明: 10 + 11 = 21
不同长度
输入: a = "1111", b = "1"
输出: "10000"
说明: 15 + 1 = 16
边界情况
输入: a = "0", b = "0"
输出: "0"
说明: 0 + 0 = 0输入: a = "1", b = "1"
输出: "10"
说明: 1 + 1 = 2

常见错误与陷阱

错误1:忘记处理最后的进位
// ❌ 错误:循环条件没有包含carry
for i >= 0 || j >= 0 {// ...
}
// 可能丢失最后的进位// ✅ 正确:包含carry条件
for i >= 0 || j >= 0 || carry > 0 {// ...
}
错误2:字符转数字错误
// ❌ 错误:直接使用字符值
sum := carry + a[i] + b[j]  // 字符值不是数字值// ✅ 正确:减去'0'转换
sum := carry + int(a[i]-'0') + int(b[j]-'0')
错误3:索引越界
// ❌ 错误:没有检查索引
sum := int(a[i]-'0') + int(b[j]-'0') + carry
// 如果i或j<0会越界// ✅ 正确:先检查索引
if i >= 0 {sum += int(a[i]-'0')
}
if j >= 0 {sum += int(b[j]-'0')
}

实战技巧总结

  1. 双指针:同时处理两个字符串,从末尾开始
  2. 进位管理:用carry变量清晰表达进位
  3. 边界检查:索引要判断>=0
  4. StringBuilder:避免字符串前插的O(n²)复杂度
  5. 反转技巧:先后插再反转,或直接前插
  6. 二进制规则:sum%2得当前位,sum/2得进位

进阶扩展

扩展1:十六进制加法
// 十六进制字符串加法
func addHex(a, b string) string {i, j := len(a)-1, len(b)-1carry := 0var result strings.Builderfor i >= 0 || j >= 0 || carry > 0 {sum := carryif i >= 0 {sum += hexToInt(a[i])i--}if j >= 0 {sum += hexToInt(b[j])j--}result.WriteByte(intToHex(sum % 16))carry = sum / 16}return reverse(result.String())
}func hexToInt(ch byte) int {if ch >= '0' && ch <= '9' {return int(ch - '0')}return int(ch-'A') + 10
}func intToHex(n int) byte {if n < 10 {return byte('0' + n)}return byte('A' + n - 10)
}
扩展2:任意进制加法
// 任意进制字符串加法
func addBase(a, b string, base int) string {i, j := len(a)-1, len(b)-1carry := 0var result strings.Builderfor i >= 0 || j >= 0 || carry > 0 {sum := carryif i >= 0 {sum += int(a[i] - '0')i--}if j >= 0 {sum += int(b[j] - '0')j--}result.WriteByte(byte('0' + sum%base))carry = sum / base}return reverse(result.String())
}
扩展3:二进制减法
// 二进制减法(a - b,假设a >= b)
func subtractBinary(a, b string) string {i, j := len(a)-1, len(b)-1borrow := 0var result strings.Builderfor i >= 0 {diff := int(a[i]-'0') - borrowif j >= 0 {diff -= int(b[j] - '0')j--}if diff < 0 {diff += 2borrow = 1} else {borrow = 0}result.WriteByte(byte('0' + diff))i--}// 移除前导零res := reverse(result.String())res = strings.TrimLeft(res, "0")if res == "" {return "0"}return res
}

应用场景

  1. 大整数运算:二进制大数加法
  2. 计算机系统:CPU ALU的加法器模拟
  3. 密码学:二进制运算基础
  4. 位运算练习:理解二进制加法原理
  5. 编译器:二进制字面量处理

代码实现

本题提供了四种不同的解法,重点掌握双指针+进位方法。

测试结果

测试用例双指针+进位StringBuilder递归实现位运算
基础测试
长度不同测试
边界测试
进位测试

核心收获

  1. 二进制加法:掌握二进制进位规则
  2. 字符串处理:双指针处理不同长度字符串
  3. 进位管理:清晰的进位逻辑
  4. 边界处理:索引检查和最后进位

应用拓展

  • 大整数二进制运算库
  • CPU加法器模拟
  • 任意进制转换和运算
  • 位运算教学工具

完整题解代码

package mainimport ("fmt""strconv""strings"
)// =========================== 方法一:双指针+进位(最优解法) ===========================// addBinary 双指针+进位+StringBuilder
// 时间复杂度:O(max(m,n)),m和n分别为两个字符串的长度
// 空间复杂度:O(1),不计结果字符串
func addBinary(a string, b string) string {i, j := len(a)-1, len(b)-1carry := 0var result strings.Builderfor i >= 0 || j >= 0 || carry > 0 {sum := carryif i >= 0 {sum += int(a[i] - '0')i--}if j >= 0 {sum += int(b[j] - '0')j--}result.WriteByte(byte('0' + sum%2))carry = sum / 2}// 反转结果res := result.String()return reverse(res)
}// reverse 反转字符串
func reverse(s string) string {runes := []rune(s)for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {runes[i], runes[j] = runes[j], runes[i]}return string(runes)
}// =========================== 方法二:直接前插(简单但效率低) ===========================// addBinary2 直接前插构建结果
// 时间复杂度:O(n²),字符串前插每次O(n)
// 空间复杂度:O(1),不计结果字符串
func addBinary2(a string, b string) string {i, j := len(a)-1, len(b)-1carry := 0result := ""for i >= 0 || j >= 0 || carry > 0 {sum := carryif i >= 0 {sum += int(a[i] - '0')i--}if j >= 0 {sum += int(b[j] - '0')j--}// 直接在前面插入result = string('0'+byte(sum%2)) + resultcarry = sum / 2}return result
}// =========================== 方法三:递归实现 ===========================// addBinary3 递归解法
// 时间复杂度:O(max(m,n))
// 空间复杂度:O(max(m,n)),递归栈空间
func addBinary3(a string, b string) string {return addHelper(a, b, len(a)-1, len(b)-1, 0)
}// addHelper 递归辅助函数
func addHelper(a, b string, i, j, carry int) string {// 递归终止条件if i < 0 && j < 0 && carry == 0 {return ""}sum := carryif i >= 0 {sum += int(a[i] - '0')}if j >= 0 {sum += int(b[j] - '0')}// 递归处理前面的位prefix := addHelper(a, b, i-1, j-1, sum/2)return prefix + string('0'+byte(sum%2))
}// =========================== 方法四:位运算(仅适用于短字符串) ===========================// addBinary4 位运算解法
// 时间复杂度:O(max(m,n))
// 空间复杂度:O(1)
// 注意:仅适用于长度<=63的字符串
func addBinary4(a string, b string) string {// 转换为整数numA := binaryToInt(a)numB := binaryToInt(b)// 使用位运算实现加法for numB != 0 {sum := numA ^ numB          // 不带进位的和carry := (numA & numB) << 1 // 进位numA = sumnumB = carry}return intToBinary(numA)
}// binaryToInt 二进制字符串转整数
func binaryToInt(s string) int64 {var result int64for _, ch := range s {result = result*2 + int64(ch-'0')}return result
}// intToBinary 整数转二进制字符串
func intToBinary(n int64) string {if n == 0 {return "0"}var result stringfor n > 0 {result = string('0'+byte(n%2)) + resultn /= 2}return result
}// =========================== 测试代码 ===========================func main() {fmt.Println("=== LeetCode 67: 二进制求和 ===\n")// 测试用例testCases := []struct {a      stringb      stringexpect string}{{"11", "1", "100"},        // 示例1: 3 + 1 = 4{"1010", "1011", "10101"}, // 示例2: 10 + 11 = 21{"0", "0", "0"},           // 边界: 0 + 0{"1", "1", "10"},          // 边界: 1 + 1 = 2{"1111", "1", "10000"},    // 不同长度: 15 + 1 = 16{"1111", "1111", "11110"}, // 相同长度: 15 + 15 = 30{"10100000100100110110010000010101111011011001101110111111111101000000101111001110001111100001101", "110101001011101110001111100110001010100001101011101010000011011011001011101111001100000011011110011", "110111101100010011000101110110100000011101000101011001000011011000001100011110011010010011000000000"}, // 长字符串{"0", "1010", "1010"},       // 一个为0{"100", "110010", "110110"}, // 不同长度{"1", "111", "1000"},        // 差距大}fmt.Println("方法一:双指针+进位+StringBuilder")runTests(testCases, addBinary)fmt.Println("\n方法二:直接前插")runTests(testCases, addBinary2)fmt.Println("\n方法三:递归实现")runTests(testCases, addBinary3)fmt.Println("\n方法四:位运算(仅短字符串)")// 只测试短字符串(长度<=63)shortCases := testCases[:len(testCases)-1] // 排除超长字符串runTests(shortCases, addBinary4)// 性能对比fmt.Println("\n=== 性能对比 ===")performanceTest()// 验证二进制计算正确性fmt.Println("\n=== 验证计算正确性 ===")verifyResults(testCases)
}// runTests 运行测试用例
func runTests(testCases []struct {a      stringb      stringexpect string
}, fn func(string, string) string) {passCount := 0for i, tc := range testCases {result := fn(tc.a, tc.b)status := "✅"if result != tc.expect {status = "❌"} else {passCount++}// 显示简化版的测试信息aDisplay := tc.abDisplay := tc.bresultDisplay := resultif len(tc.a) > 20 {aDisplay = tc.a[:17] + "..."}if len(tc.b) > 20 {bDisplay = tc.b[:17] + "..."}if len(result) > 20 {resultDisplay = result[:17] + "..."}fmt.Printf("  测试%d: %s\n", i+1, status)fmt.Printf("    输入: a=%s, b=%s\n", aDisplay, bDisplay)fmt.Printf("    输出: %s\n", resultDisplay)if result != tc.expect {expectDisplay := tc.expectif len(tc.expect) > 20 {expectDisplay = tc.expect[:17] + "..."}fmt.Printf("    期望: %s\n", expectDisplay)}}fmt.Printf("  通过: %d/%d\n", passCount, len(testCases))
}// performanceTest 性能测试
func performanceTest() {// 生成长字符串a := strings.Repeat("1", 1000)b := strings.Repeat("1", 1000)fmt.Println("  测试数据:两个1000位的全1二进制字符串")// 测试方法一result1 := addBinary(a, b)fmt.Printf("  方法一(双指针+StringBuilder): 结果长度=%d\n", len(result1))// 测试方法二(可能较慢)result2 := addBinary2(a, b)fmt.Printf("  方法二(直接前插): 结果长度=%d\n", len(result2))// 测试方法三result3 := addBinary3(a, b)fmt.Printf("  方法三(递归): 结果长度=%d\n", len(result3))fmt.Println("  注:方法四(位运算)不适用于长字符串,会溢出")
}// verifyResults 验证结果正确性
func verifyResults(testCases []struct {a      stringb      stringexpect string
}) {for i, tc := range testCases {// 跳过超长字符串(无法转换为int64)if len(tc.a) > 60 || len(tc.b) > 60 {continue}// 转换为十进制验证numA, _ := strconv.ParseInt(tc.a, 2, 64)numB, _ := strconv.ParseInt(tc.b, 2, 64)expected, _ := strconv.ParseInt(tc.expect, 2, 64)sum := numA + numBstatus := "✅"if sum != expected {status = "❌"}fmt.Printf("  验证%d: %s %d + %d = %d (期望%d)\n",i+1, status, numA, numB, sum, expected)}
}
http://www.dtcms.com/a/474755.html

相关文章:

  • 使用Onnxruntime对onnx模型量化介绍
  • 新乡专业做网站多少钱青岛李沧区网站建设
  • 线段树算法详解与实现
  • 前端开发入门:什么是前端?它有什么用?前端开发入门
  • 不开网店怎么做淘宝网站网站seo的主要优化内容
  • 基于 WebSocket 协议的实时弹幕通信机制分析-抖音
  • 做网站推广需要具备哪些条件WordPress已安装主题
  • vector、list、deque的差异
  • 开设网站维护公司个人网页主页
  • devexpress做网站简单门户网站模板
  • 没有注册公司可以建网站吗企业网站开发公司-北京公司
  • 企业级文件共享服务全解析:NFS 和 Samba 搭建指南与 Windows 乱码解决
  • Docker中的无法正常使用os.system
  • 中济建设官方网站慧聪网登录
  • 北京网站建设的网站的设计页面
  • python中mod函数怎么用
  • 996引擎-批量复制图集文件
  • 01、如何学习单片机
  • 【技术文档:Dify 本地 Docker 环境邮件服务排错指南】
  • 安装 Win10/11 系统下 WSL2+Ubuntu20.04
  • 苏州专业做网站比较好的公司汕头百姓网交友
  • Git简介及安装
  • Git版本控制工具合并分支merge命令操作流程
  • SAP Business Suite:引领企业AI从工具到智能协同的全面转型
  • 门户网站申请ppt模板免费下载 动态
  • 网站建设模版文档WordPress修改前端
  • 数据结构<c语言>——串
  • 基于单片机的16位逐次逼近AD电路设计
  • 网站建设交流会石狮建设网站
  • 小白也能开发 Chrome 插件