【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,记录每次相加的进位
- 同时遍历两个链表,处理不同长度的情况
- 最后检查是否还有进位需要处理
算法步骤:
- 初始化虚拟头节点dummy和进位变量carry = 0
- 同时遍历两个链表l1和l2:
- 获取当前位的值(如果链表已结束则为0)
- 计算当前位的和:sum = val1 + val2 + carry
- 计算进位:carry = sum / 10
- 创建新节点存储当前位:sum % 10
- 如果遍历结束后还有进位,创建新节点存储进位
- 返回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
链表相加示例
进位处理流程图
不同长度链表处理
递归解法调用栈
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
边界情况处理
- 链表长度不同:短链表用0补齐
- 进位处理:最后检查是否还有进位需要处理
- 空链表:返回另一个链表或空链表
- 全为0:返回[0]
- 大数相加:处理进位溢出
测试用例
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))
}
关键技巧
- 虚拟头节点:使用dummy节点简化链表操作
- 进位处理:维护carry变量,处理进位逻辑
- 长度处理:同时遍历两个链表,短链表用0补齐
- 边界检查:最后检查是否还有进位需要处理
- 节点创建:每次创建新节点存储当前位的结果
实际应用
- 大数运算:处理超出基本数据类型范围的数字运算
- 金融计算:高精度货币计算
- 科学计算:需要高精度的数学运算
- 密码学:大整数运算
完整题解代码
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("❌ 大链表结果验证失败!")}
}