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

【LeetCode】2. 两数相加

文章目录

  • 2. 两数相加
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 示例 3:
    • 示例 4:
    • 提示:
    • 解题思路
      • 方法一:模拟加法(推荐)
      • 方法二:递归解法
      • 方法三:转换为数字后相加
    • 代码实现
    • 复杂度分析
    • 算法图解
      • 模拟加法过程
      • 链表相加示例
      • 进位处理流程图
      • 不同长度链表处理
      • 递归解法调用栈
    • 边界情况处理
    • 测试用例
    • 关键技巧
    • 实际应用
    • 完整题解代码

2. 两数相加

题目描述

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/add-two-numbers
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


示例 1:

在这里插入图片描述

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

示例 2:

输入:l1 = [0], l2 = [0]
输出:[0]

示例 3:

输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]

示例 4:

输入:l1 = [1,2,3], l2 = [4,5,6]
输出:[5,7,9]
解释:321 + 654 = 975

提示:

  • 每个链表中的节点数在范围 [1, 100] 内
  • 0 <= Node.val <= 9
  • 题目数据保证列表表示的数字不含前导零

解题思路

方法一:模拟加法(推荐)

核心思想

  • 模拟手工加法的过程,从最低位开始逐位相加
  • 维护一个进位变量carry,记录每次相加的进位
  • 同时遍历两个链表,处理不同长度的情况
  • 最后检查是否还有进位需要处理

算法步骤

  1. 初始化虚拟头节点dummy和进位变量carry = 0
  2. 同时遍历两个链表l1和l2:
    • 获取当前位的值(如果链表已结束则为0)
    • 计算当前位的和:sum = val1 + val2 + carry
    • 计算进位:carry = sum / 10
    • 创建新节点存储当前位:sum % 10
  3. 如果遍历结束后还有进位,创建新节点存储进位
  4. 返回dummy.Next

时间复杂度:O(max(m,n)),其中m和n是两个链表的长度
空间复杂度:O(max(m,n)),需要创建新的链表存储结果

方法二:递归解法

核心思想

  • 使用递归的方式处理链表相加
  • 每次递归处理当前位,然后递归处理下一位
  • 递归终止条件是两个链表都为空且没有进位

时间复杂度:O(max(m,n))
空间复杂度:O(max(m,n)),递归调用栈的深度

方法三:转换为数字后相加

核心思想

  • 将两个链表转换为数字
  • 进行数字相加
  • 将结果转换回链表

注意:这种方法只适用于链表长度较小的情况,因为大数会溢出

时间复杂度:O(m+n)
空间复杂度:O(max(m,n))

代码实现

// 链表节点定义
type ListNode struct {Val  intNext *ListNode
}// 模拟加法解法(推荐)
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {dummy := &ListNode{Val: 0} // 虚拟头节点current := dummycarry := 0// 同时遍历两个链表for l1 != nil || l2 != nil || carry > 0 {val1, val2 := 0, 0if l1 != nil {val1 = l1.Vall1 = l1.Next}if l2 != nil {val2 = l2.Vall2 = l2.Next}// 计算当前位的和和进位sum := val1 + val2 + carrycarry = sum / 10// 创建新节点current.Next = &ListNode{Val: sum % 10}current = current.Next}return dummy.Next
}

复杂度分析

方法时间复杂度空间复杂度适用场景
模拟加法O(max(m,n))O(max(m,n))推荐,最优解
递归解法O(max(m,n))O(max(m,n))递归思维清晰
转换为数字O(m+n)O(max(m,n))小规模数据

算法图解

模拟加法过程

graph TDA[开始: l1=[2,4,3], l2=[5,6,4]] --> B[初始化: dummy, carry=0]B --> C[第1位: 2+5+0=7, carry=0]C --> D[创建节点7, current指向7]D --> E[第2位: 4+6+0=10, carry=1]E --> F[创建节点0, current指向0]F --> G[第3位: 3+4+1=8, carry=0]G --> H[创建节点8, current指向8]H --> I[检查进位: carry=0, 结束]I --> J[返回结果: [7,0,8]]style J fill:#90EE90style A fill:#E6F3FFstyle B fill:#FFF2CC

链表相加示例

加法过程
第1位: 2+5=7
第2位: 4+6=10, 进位1
第3位: 3+4+1=8
l1: [2,4,3]
l2: [5,6,4]
逐位相加
结果: [7,0,8]

进位处理流程图

开始处理当前位
l1和l2都为空?
carry > 0?
创建进位节点
结束
获取当前位值
计算sum = val1 + val2 + carry
计算新进位: carry = sum/10
创建节点存储: sum%10
移动到下一位

不同长度链表处理

处理过程
前4位: 9999+9999=19998
后3位: 999+0=999
进位处理: 1
l1: [9,9,9,9,9,9,9]
l2: [9,9,9,9]
处理前4位
结果: [8,9,9,9,0,0,0,1]

递归解法调用栈

graph TDA[addTwoNumbers(l1, l2, 0)] --> B[处理第1位: 2+5+0=7]B --> C[递归: addTwoNumbers(l1.Next, l2.Next, 0)]C --> D[处理第2位: 4+6+0=10]D --> E[递归: addTwoNumbers(l1.Next, l2.Next, 1)]E --> F[处理第3位: 3+4+1=8]F --> G[递归: addTwoNumbers(nil, nil, 0)]G --> H[返回nil]H --> I[构建结果链表]style I fill:#90EE90style A fill:#E6F3FF

边界情况处理

  1. 链表长度不同:短链表用0补齐
  2. 进位处理:最后检查是否还有进位需要处理
  3. 空链表:返回另一个链表或空链表
  4. 全为0:返回[0]
  5. 大数相加:处理进位溢出

测试用例

func main() {// 测试用例1l1 := buildList([]int{2, 4, 3})l2 := buildList([]int{5, 6, 4})result1 := addTwoNumbers(l1, l2)fmt.Printf("测试用例1: l1=[2,4,3], l2=[5,6,4]\n")fmt.Printf("结果: %v\n", listToSlice(result1))// 测试用例2l3 := buildList([]int{0})l4 := buildList([]int{0})result2 := addTwoNumbers(l3, l4)fmt.Printf("测试用例2: l1=[0], l2=[0]\n")fmt.Printf("结果: %v\n", listToSlice(result2))// 测试用例3l5 := buildList([]int{9, 9, 9, 9, 9, 9, 9})l6 := buildList([]int{9, 9, 9, 9})result3 := addTwoNumbers(l5, l6)fmt.Printf("测试用例3: l1=[9,9,9,9,9,9,9], l2=[9,9,9,9]\n")fmt.Printf("结果: %v\n", listToSlice(result3))// 边界测试l7 := buildList([]int{1, 2, 3})l8 := buildList([]int{4, 5, 6})result4 := addTwoNumbers(l7, l8)fmt.Printf("边界测试: l1=[1,2,3], l2=[4,5,6]\n")fmt.Printf("结果: %v\n", listToSlice(result4))
}

关键技巧

  1. 虚拟头节点:使用dummy节点简化链表操作
  2. 进位处理:维护carry变量,处理进位逻辑
  3. 长度处理:同时遍历两个链表,短链表用0补齐
  4. 边界检查:最后检查是否还有进位需要处理
  5. 节点创建:每次创建新节点存储当前位的结果

实际应用

  • 大数运算:处理超出基本数据类型范围的数字运算
  • 金融计算:高精度货币计算
  • 科学计算:需要高精度的数学运算
  • 密码学:大整数运算

完整题解代码

package mainimport ("fmt""strconv""strings"
)// 链表节点定义
type ListNode struct {Val  intNext *ListNode
}// 方法一:模拟加法解法(推荐)
// 时间复杂度:O(max(m,n)),空间复杂度:O(max(m,n))
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {dummy := &ListNode{Val: 0} // 虚拟头节点current := dummycarry := 0// 同时遍历两个链表for l1 != nil || l2 != nil || carry > 0 {val1, val2 := 0, 0if l1 != nil {val1 = l1.Vall1 = l1.Next}if l2 != nil {val2 = l2.Vall2 = l2.Next}// 计算当前位的和和进位sum := val1 + val2 + carrycarry = sum / 10// 创建新节点current.Next = &ListNode{Val: sum % 10}current = current.Next}return dummy.Next
}// 方法二:递归解法
// 时间复杂度:O(max(m,n)),空间复杂度:O(max(m,n))
func addTwoNumbersRecursive(l1 *ListNode, l2 *ListNode) *ListNode {return addTwoNumbersWithCarry(l1, l2, 0)
}func addTwoNumbersWithCarry(l1 *ListNode, l2 *ListNode, carry int) *ListNode {// 递归终止条件if l1 == nil && l2 == nil && carry == 0 {return nil}// 计算当前位的值val1, val2 := 0, 0if l1 != nil {val1 = l1.Vall1 = l1.Next}if l2 != nil {val2 = l2.Vall2 = l2.Next}// 计算当前位的和和进位sum := val1 + val2 + carrynewCarry := sum / 10currentVal := sum % 10// 创建当前节点并递归处理下一位current := &ListNode{Val: currentVal}current.Next = addTwoNumbersWithCarry(l1, l2, newCarry)return current
}// 方法三:转换为数字后相加(仅适用于小规模数据)
// 时间复杂度:O(m+n),空间复杂度:O(max(m,n))
func addTwoNumbersConvert(l1 *ListNode, l2 *ListNode) *ListNode {// 将链表转换为数字num1 := listToNumber(l1)num2 := listToNumber(l2)// 数字相加sum := num1 + num2// 将结果转换回链表return numberToList(sum)
}// 辅助函数:将链表转换为数字
func listToNumber(head *ListNode) int {if head == nil {return 0}var result strings.Buildercurrent := head// 从链表头部开始构建数字字符串(逆序)for current != nil {result.WriteString(strconv.Itoa(current.Val))current = current.Next}// 反转字符串得到正确的数字numStr := result.String()num, _ := strconv.Atoi(reverseString(numStr))return num
}// 辅助函数:将数字转换为链表
func numberToList(num int) *ListNode {if num == 0 {return &ListNode{Val: 0}}// 转换为字符串numStr := strconv.Itoa(num)// 创建虚拟头节点dummy := &ListNode{Val: 0}current := dummy// 从右到左(低位到高位)创建节点for i := len(numStr) - 1; i >= 0; i-- {digit, _ := strconv.Atoi(string(numStr[i]))current.Next = &ListNode{Val: digit}current = current.Next}return dummy.Next
}// 辅助函数:反转字符串
func reverseString(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)
}// 方法四:优化的模拟加法解法
func addTwoNumbersOptimized(l1 *ListNode, l2 *ListNode) *ListNode {dummy := &ListNode{Val: 0}current := dummycarry := 0// 优化:先处理两个链表都有的部分for l1 != nil && l2 != nil {sum := l1.Val + l2.Val + carrycarry = sum / 10current.Next = &ListNode{Val: sum % 10}current = current.Nextl1 = l1.Nextl2 = l2.Next}// 处理剩余部分remaining := l1if l2 != nil {remaining = l2}for remaining != nil {sum := remaining.Val + carrycarry = sum / 10current.Next = &ListNode{Val: sum % 10}current = current.Nextremaining = remaining.Next}// 处理最后的进位if carry > 0 {current.Next = &ListNode{Val: carry}}return dummy.Next
}// 辅助函数:从数组构建链表
func buildList(nums []int) *ListNode {if len(nums) == 0 {return nil}dummy := &ListNode{Val: 0}current := dummyfor _, num := range nums {current.Next = &ListNode{Val: num}current = current.Next}return dummy.Next
}// 辅助函数:将链表转换为数组
func listToSlice(head *ListNode) []int {var result []intcurrent := headfor current != nil {result = append(result, current.Val)current = current.Next}return result
}// 辅助函数:打印链表
func printList(head *ListNode, name string) {fmt.Printf("%s: %v\n", name, listToSlice(head))
}// 辅助函数:验证结果是否正确
func validateResult(l1 *ListNode, l2 *ListNode, result *ListNode) bool {// 将链表转换为数字进行验证num1 := listToNumber(l1)num2 := listToNumber(l2)expectedSum := num1 + num2actualSum := listToNumber(result)return expectedSum == actualSum
}// 辅助函数:比较两个链表是否相等
func compareLists(l1 *ListNode, l2 *ListNode) bool {for l1 != nil && l2 != nil {if l1.Val != l2.Val {return false}l1 = l1.Nextl2 = l2.Next}return l1 == nil && l2 == nil
}func main() {fmt.Println("=== 2. 两数相加 ===")// 测试用例1l1 := buildList([]int{2, 4, 3})l2 := buildList([]int{5, 6, 4})fmt.Printf("测试用例1: l1=[2,4,3], l2=[5,6,4]\n")printList(l1, "l1")printList(l2, "l2")result1 := addTwoNumbers(l1, l2)fmt.Printf("模拟加法解法结果: %v\n", listToSlice(result1))result1Recursive := addTwoNumbersRecursive(l1, l2)fmt.Printf("递归解法结果: %v\n", listToSlice(result1Recursive))result1Optimized := addTwoNumbersOptimized(l1, l2)fmt.Printf("优化解法结果: %v\n", listToSlice(result1Optimized))// 验证结果if validateResult(l1, l2, result1) {fmt.Println("✅ 结果验证通过!")} else {fmt.Println("❌ 结果验证失败!")}fmt.Println()// 测试用例2l3 := buildList([]int{0})l4 := buildList([]int{0})fmt.Printf("测试用例2: l1=[0], l2=[0]\n")printList(l3, "l1")printList(l4, "l2")result2 := addTwoNumbers(l3, l4)fmt.Printf("模拟加法解法结果: %v\n", listToSlice(result2))fmt.Println()// 测试用例3l5 := buildList([]int{9, 9, 9, 9, 9, 9, 9})l6 := buildList([]int{9, 9, 9, 9})fmt.Printf("测试用例3: l1=[9,9,9,9,9,9,9], l2=[9,9,9,9]\n")printList(l5, "l1")printList(l6, "l2")result3 := addTwoNumbers(l5, l6)fmt.Printf("模拟加法解法结果: %v\n", listToSlice(result3))// 验证结果if validateResult(l5, l6, result3) {fmt.Println("✅ 结果验证通过!")} else {fmt.Println("❌ 结果验证失败!")}fmt.Println()// 边界测试用例testCases := []struct {l1   []intl2   []intdesc string}{{[]int{1, 2, 3}, []int{4, 5, 6}, "边界测试1"},{[]int{1}, []int{9, 9, 9}, "边界测试2"},{[]int{9, 9, 9}, []int{1}, "边界测试3"},{[]int{1, 2, 3, 4, 5}, []int{5, 4, 3, 2, 1}, "边界测试4"},}for _, tc := range testCases {ll1 := buildList(tc.l1)ll2 := buildList(tc.l2)fmt.Printf("%s: l1=%v, l2=%v\n", tc.desc, tc.l1, tc.l2)result := addTwoNumbers(ll1, ll2)fmt.Printf("结果: %v\n", listToSlice(result))// 验证结果if validateResult(ll1, ll2, result) {fmt.Println("✅ 验证通过")} else {fmt.Println("❌ 验证失败")}fmt.Println()}// 算法正确性验证fmt.Println("=== 算法正确性验证 ===")verifyL1 := buildList([]int{2, 4, 3})verifyL2 := buildList([]int{5, 6, 4})fmt.Printf("验证链表: l1=%v, l2=%v\n", listToSlice(verifyL1), listToSlice(verifyL2))verifyResult1 := addTwoNumbers(verifyL1, verifyL2)verifyResult2 := addTwoNumbersRecursive(verifyL1, verifyL2)verifyResult3 := addTwoNumbersOptimized(verifyL1, verifyL2)fmt.Printf("模拟加法解法: %v\n", listToSlice(verifyResult1))fmt.Printf("递归解法: %v\n", listToSlice(verifyResult2))fmt.Printf("优化解法: %v\n", listToSlice(verifyResult3))// 验证所有解法结果一致if compareLists(verifyResult1, verifyResult2) && compareLists(verifyResult2, verifyResult3) {fmt.Println("✅ 所有解法结果一致!")// 验证结果正确性if validateResult(verifyL1, verifyL2, verifyResult1) {fmt.Println("✅ 结果验证通过!")} else {fmt.Println("❌ 结果验证失败!")}} else {fmt.Println("❌ 解法结果不一致,需要检查!")}// 性能测试fmt.Println("\n=== 性能测试 ===")// 创建大链表进行测试largeL1 := buildList(make([]int, 1000))largeL2 := buildList(make([]int, 1000))// 填充一些测试数据current := largeL1for i := 0; i < 1000; i++ {current.Val = i % 10current = current.Next}current = largeL2for i := 0; i < 1000; i++ {current.Val = (i + 5) % 10current = current.Next}fmt.Printf("大链表测试: 长度=%d\n", 1000)result := addTwoNumbers(largeL1, largeL2)fmt.Printf("模拟加法解法结果长度: %d\n", len(listToSlice(result)))// 验证大链表结果if validateResult(largeL1, largeL2, result) {fmt.Println("✅ 大链表结果验证通过!")} else {fmt.Println("❌ 大链表结果验证失败!")}
}
http://www.dtcms.com/a/327175.html

相关文章:

  • 一台云主机“被黑”后的 24 小时排查手记
  • 【力扣 Hot100】刷题日记
  • 《Redis ACL验证流程:从用户认证到权限检查的完整步骤》
  • 【doris基础与进阶】3-Doris安装与部署
  • 模板打印技术——自动识别office类型 打印模板:为政务土地确权定制的替换利器—仙盟创梦IDE
  • Go 语言 里 `var`、`make`、`new`、`:=` 的区别
  • Python 标准库模块shutil
  • 当多模态大语言模型遇上视觉难题!AI视觉探索之旅
  • 基于Hadoop的全国农产品批发价格数据分析与可视化与价格预测研究
  • grpc浅入门
  • jdk升级
  • 【Redis在在线表单提交防重复机制中的应用策略】
  • 【开发环境下浏览器前后端Cookie跨域问题】
  • 实现文字在块元素中水平/垂直居中详解
  • 深度贴:前端网络基础及进阶(3)
  • Linux 常用命令大全:覆盖日常 99% 操作需求
  • 【SpringBoot】05 容器功能 - SpringBoot底层注解的应用与实战 - @Configuration + @Bean
  • WebAssembly的原理与使用
  • Day24|学习前端CSS
  • 虚拟机高级玩法-网页也能运行虚拟机——WebAssembly
  • GitHub的简单使用方法----(4)
  • Seata深度剖析:微服务分布式事务解决方案
  • 如何应对CAN总线冲突和数据丢包
  • GitHub Browser-Use 的部署失败记录:失败了,失败了。。。。
  • 在 Windows 上升级 Python 到 3.8 的步骤
  • 【微服务过度拆分的问题】
  • IEEE 2025 | 重磅开源!SLAM框架用“法向量+LRU缓存”,将三维重建效率飙升72%!
  • 学习嵌入式的第十七天——Linux编程——shell编程
  • 【图像算法 - 13】基于 YOLO12 与 OpenCV 的实时目标点击跟踪系统(系统介绍 + 源码详细)
  • centos 怎么部署 vscode 网页版