【经典算法】LeetCode 2两数相加(Java/C/Python3/Go实现含注释说明,中等)

简介: 【经典算法】LeetCode 2两数相加(Java/C/Python3/Go实现含注释说明,中等)
  • 作者简介:阿里非典型程序员一枚 ,记录在大厂的打怪升级之路。 一起学习Java、大数据、数据结构算法(公众号同名
  • ❤️觉得文章还不错的话欢迎大家点赞👍➕收藏⭐️➕评论,💬支持博主,记得点个大大的关注,持续更新🤞
    ————————————————-

题目描述

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

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

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

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)

输出:7 -> 0 -> 8

解释:342 + 465 = 807

原题:LeetCode 2

思路及实现

方式一:模拟手工加法

思路

我们可以模拟手工加法的过程。从头开始遍历两个链表,将对应位置的数字相加,并处理进位。如果两个链表的长度不同,则较短的链表后面视为0。

代码实现

Java版本
public class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    // 哑节点,方便处理头节点
    ListNode dummy = new ListNode(0);
    ListNode curr = dummy;
    int carry = 0;
    while (l1 != null || l2 != null) {
        int sum = carry;
        if (l1 != null) {
            sum += l1.val;
            l1 = l1.next;
        }
        if (l2 != null) {
            sum += l2.val;
            l2 = l2.next;
        }
        carry = sum / 10;
        curr.next = new ListNode(sum % 10);
        curr = curr.next;
    }
    // 如果最后还有进位,需要额外添加一个节点
    if (carry > 0) {
        curr.next = new ListNode(carry);
    }
    return dummy.next;
}

说明:

此代码使用了一个哑节点dummy,它的next指向真正的头节点,方便我们处理链表的头节点。在遍历链表时,我们计算当前位置的和sum,包括进位carry。然后将和的个位数创建新的节点,curr指向新的节点继续遍历。最后,如果遍历完两个链表后还有进位,需要额外添加一个节点。

C语言版本
typedef struct ListNode {
    int val;
    struct ListNode *next;
} ListNode;
ListNode* createNode(int val) {
    ListNode* node = (ListNode*)malloc(sizeof(ListNode));
    node->val = val;
    node->next = NULL;
    return node;
}
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
    ListNode* dummy = createNode(0);
    ListNode* curr = dummy;
    int carry = 0;
    while (l1 != NULL || l2 != NULL) {
        int sum = carry;
        if (l1 != NULL) {
            sum += l1->val;
            l1 = l1->next;
        }
        if (l2 != NULL) {
            sum += l2->val;
            l2 = l2->next;
        }
        carry = sum / 10;
        curr->next = createNode(sum % 10);
        curr = curr->next;
    }
    if (carry > 0) {
        curr->next = createNode(carry);
    }
    return dummy->next;
}

说明:

C语言版本与Java版本类似,不过需要注意C语言需要手动分配内存,并且使用malloc来创建新的节点。

Python3版本
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None
def addTwoNumbers(l1: ListNode, l2: ListNode) -> ListNode:
    dummy = ListNode(0)
    curr = dummy
    carry = 0
    while l1 or l2:
        sum_val = carry
        if l1:
            sum_val += l1.val
            l1 = ll1.next
        if l2:
            sum_val += l2.val
            l2 = l2.next
        carry = sum_val // 10
        curr.next = ListNode(sum_val % 10)
        curr = curr.next
    if carry > 0:
        curr.next = ListNode(carry)
    return dummy.next

说明:

Python版本的代码结构与其他版本相似,但语法更简洁。在Python中,我们不需要显式地分配内存或管理指针,因为Python会自动处理这些。

Golang版本
package main
import "fmt"
type ListNode struct {
    Val  int
    Next *ListNode
}
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
    dummy := &ListNode{}
    curr := dummy
    carry := 0
    for l1 != nil || l2 != nil {
        sum := carry
        if l1 != nil {
            sum += l1.Val
            l1 = l1.Next
        }
        if l2 != nil {
            sum += l2.Val
            l2 = l2.Next
        }
        carry = sum / 10
        curr.Next = &ListNode{Val: sum % 10}
        curr = curr.Next
    }
    if carry > 0 {
        curr.Next = &ListNode{Val: carry}
    }
    return dummy.Next
}
func main() {
    // 测试代码
    // 创建链表 l1: 2 -> 4 -> 3
    l1 := &ListNode{Val: 2}
    l1.Next = &ListNode{Val: 4}
    l1.Next.Next = &ListNode{Val: 3}
    // 创建链表 l2: 5 -> 6 -> 4
    l2 := &ListNode{Val: 5}
    l2.Next = &ListNode{Val: 6}
    l2.Next.Next = &ListNode{Val: 4}
    // 调用函数
    result := addTwoNumbers(l1, l2)
    // 打印结果
    for result != nil {
        fmt.Print(result.Val, " ")
        result = result.Next
    }
}

说明:

Golang版本同样使用了哑节点,代码结构和思路与其他版本一致。在Go中,我们使用指针来操作链表节点。

复杂度分析

  • 时间复杂度:O(max(m, n)),其中m和n分别为两个链表的长度。我们需要遍历两个链表中的所有节点一次。
  • 空间复杂度:O(max(m, n))。在最坏的情况下,当两个链表的长度不同时,结果链表的长度将等于较长链表的长度,因此需要额外分配相应数量的节点。

方式二:递归

思路

递归方式也可以解决这个问题。我们可以递归地调用addTwoNumbers函数来处理链表的剩余部分,并将结果和进位传递回上一层递归。

代码实现

Java版本
public class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}
public class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        return addTwoNumbersRecursive(l1, l2, 0);
    }
    
    private ListNode addTwoNumbersRecursive(ListNode l1, ListNode l2, int carry) {
        if (l1 == null && l2 == null && carry == 0) {
            return null;
        }
        
        int val1 = (l1 != null) ? l1.val : 0;
        int val2 = (l2 != null) ? l2.val : 0;
        int sum = val1 + val2 + carry;
        
        ListNode newNode = new ListNode(sum % 10);
        newNode.next = addTwoNumbersRecursive(l1 != null ? l1.next : null, l2 != null ? l2.next : null, sum / 10);
        
        return newNode;
    }
}

说明:

如果其中一个链表为空,直接返回另一个链表。计算当前节点的和,并创建新的头节点。递归地处理链表剩余部分,并将结果连接到新头节点。如果当前和大于等于10,则处理进位。

C语言版本
#include <stdio.h>
#include <stdlib.h>
struct ListNode {
    int val;
    struct ListNode *next;
};
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) {
    struct ListNode* head = NULL;
    struct ListNode** node = &head;
    int carry = 0;
    while (l1 || l2 || carry) {
        int sum = carry;
        if (l1) {
            sum += l1->val;
            l1 = l1->next;
        }
        if (l2) {
            sum += l2->val;
            l2 = l2->next;
        }
        carry = sum / 10;
        *node = malloc(sizeof(struct ListNode));
        (*node)->val = sum % 10;
        (*node)->next = NULL;
        node = &((*node)->next);
    }
    return head;
}

说明:

在C语言中,初始化头结点和当前处理结点:使用head变量来记录结果链表的头部,用node指针变量指向当前链表节点的地址,初始指向头结点地址。

遍历两个链表:当l1、l2或carry(进位)非空时,循环继续。每一步计算当前和及进位。

更新节点与进位:分别从l1和l2取值相加,再加上之前的进位值。计算后得到新的进位值和当前节点的值。

构建结果链表:为当前和的余数创建新节点,连接到结果链表末尾。

返回结果:返回头结点head。

C++版本

C++的实现更为简洁,主要是因为C++具备自动内存管理和类的特性,使得代码更易写和理解。

#include <iostream>
class ListNode {
public:
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
    ListNode *head = new ListNode(0), *node = head;
    int carry = 0;
    while (l1 || l2 || carry) {
        int sum = (l1 ? l1->val : 0) + (l2 ? l2->val : 0) + carry;
        carry = sum / 10;
        node->next = new ListNode(sum % 10);
        node = node->next;
        
        if (l1) l1 = l1->next;
        if (l2) l2 = l2->next;
    }
    ListNode *toDelete = head;
    head = head->next;
    delete toDelete; // 删除哑节点
    return head;
}

说明

使用哑节点:这里创建了一个哑节点head来简化边界条件处理,便于在链表前添加节点。实际返回的是head->next。

处理两个链表和进位:循环继绀直到l1、l2和carry都处理完毕。在每一步,计算当前位置的值和新的进位。

添加新节点:根据每一步计算的结果创建新节点并链接到结果链表。

清理哑节点:在返回结果前,删除初始化时创建的哑节点。

Python3

在Python中,可以使用递归实现链表的相加。Python中链表节点通常通过类来实现,下面是一个示例:

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next
def addTwoNumbers(l1, l2, carry=0):
    if not l1 and not l2 and not carry:
        return None
    val1 = l1.val if l1 else 0
    val2 = l2.val if l2 else 0
    carry, out = divmod(val1 + val2 + carry, 10)
    currentNode = ListNode(out)
    currentNode.next = addTwoNumbers(l1.next if l1 else None, l2.next if l2 else None, carry)
    return currentNode
Go语言

在Go语言中,链表节点通常通过结构体来实现,下面是一个递归实现链表相加的示例:

type ListNode struct {
    Val int
    Next *ListNode
}
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
    return addTwoNumbersRecursive(l1, l2, 0)
}
func addTwoNumbersRecursive(l1 *ListNode, l2 *ListNode, carry int) *ListNode {
    if l1 == nil && l2 == nil && carry == 0 {
        return nil
    }
    
    val1, val2 := 0, 0
    if l1 != nil {
        val1 = l1.Val
    }
    if l2 != nil {
        val2 = l2.Val
    }
    sum := val1 + val2 + carry
    
    newNode := &ListNode{Val: sum % 10}
    if l1 != nil || l2 != nil || sum >= 10 {
        if l1 != nil {
            l1 = l1.Next
        }
        if l2 != nil {
            l2 = l2.Next
        }
        newNode.Next = addTwoNumbersRecursive(l1, l2, sum/10)
    }
    return newNode
}

说明

请注意,在Go语言中,通常使用指针来操作链表节点,以便能够修改链表的结构。在上面的代码中,addTwoNumbers 函数接收两个 *ListNode 类型的参数,即链表的头节点指针,并返回一个新的链表头节点指针。

递归函数addTwoNumbers计算当前节点的和,并递归地处理剩余节点。在递归调用中,我们传递了下一个节点的指针,并在每个递归步骤中处理当前节点的和。carry函数用于处理链表尾部可能出现的进位。

printList函数用于打印链表,它遍历链表并打印每个节点的值,直到链表末尾。 在main函数中,我们创建了两个示例链表l1l2,然后调用addTwoNumbers函数计算它们的和,并使用printList`函数打印结果链表。

复杂度分析

  • 时间复杂度:O(max(m, n)),其中m和n分别为两个链表的长度。每个节点最多被遍历一次。
  • 空间复杂度:O(max(m, n))。由于使用了递归,函数调用的深度最坏情况下是链表的长度。因此,递归会占用与链表长度相同的栈空间。注意,这并不包括创建结果链表时分配的额外空间。如果只考虑栈空间,复杂度为O(max(m, n));但如果同时考虑结果链表,则空间复杂度仍然是O(max(m, n))。

总结

方式 优点 缺点 时间复杂度 空间复杂度 其他
迭代方式 - 直观易懂,易于实现 - 代码相对较长 O(max(m, n)) O(max(m, n)) m和n分别为两个链表的长度
递归方式 - 代码简洁,易于理解 - 可能存在栈溢出风险(链表过长时) O(max(m, n)) O(max(m, n))(递归栈空间)或 O(1)(不考虑递归栈) 递归深度受链表长度限制

相似题目

相似题目 难度 链接
leetcode 445. 两数相加 II 中等 力扣-445
leetcode 67. 二进制求和 简单 力扣-67
leetcode 369. 给定数字的频率将数字排序 中等 力扣-369
leetcode 349. 两个数组的交集 简单 力扣-349

这些相似题目涉及到加法运算、链表操作、数组操作等,对于理解链表相加问题及其变种有一定的帮助。通过解决这些相似题目,可以加深对链表、数组等数据结构以及加法运算的理解和应用。

欢迎一键三连(关注+点赞+收藏),技术的路上一起加油!!!代码改变世界

  • 关于我:阿里非典型程序员一枚 ,记录在大厂的打怪升级之路。 一起学习Java、大数据、数据结构算法公众号同名),回复暗号,更能获取学习秘籍和书籍等
相关文章
|
14天前
|
机器学习/深度学习 算法 TensorFlow
动物识别系统Python+卷积神经网络算法+TensorFlow+人工智能+图像识别+计算机毕业设计项目
动物识别系统。本项目以Python作为主要编程语言,并基于TensorFlow搭建ResNet50卷积神经网络算法模型,通过收集4种常见的动物图像数据集(猫、狗、鸡、马)然后进行模型训练,得到一个识别精度较高的模型文件,然后保存为本地格式的H5格式文件。再基于Django开发Web网页端操作界面,实现用户上传一张动物图片,识别其名称。
44 1
动物识别系统Python+卷积神经网络算法+TensorFlow+人工智能+图像识别+计算机毕业设计项目
|
13天前
|
机器学习/深度学习 人工智能 算法
植物病害识别系统Python+卷积神经网络算法+图像识别+人工智能项目+深度学习项目+计算机课设项目+Django网页界面
植物病害识别系统。本系统使用Python作为主要编程语言,通过收集水稻常见的四种叶片病害图片('细菌性叶枯病', '稻瘟病', '褐斑病', '稻瘟条纹病毒病')作为后面模型训练用到的数据集。然后使用TensorFlow搭建卷积神经网络算法模型,并进行多轮迭代训练,最后得到一个识别精度较高的算法模型,然后将其保存为h5格式的本地模型文件。再使用Django搭建Web网页平台操作界面,实现用户上传一张测试图片识别其名称。
65 21
植物病害识别系统Python+卷积神经网络算法+图像识别+人工智能项目+深度学习项目+计算机课设项目+Django网页界面
|
13天前
|
机器学习/深度学习 人工智能 算法
鸟类识别系统Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+ResNet50算法模型+图像识别
鸟类识别系统。本系统采用Python作为主要开发语言,通过使用加利福利亚大学开源的200种鸟类图像作为数据集。使用TensorFlow搭建ResNet50卷积神经网络算法模型,然后进行模型的迭代训练,得到一个识别精度较高的模型,然后在保存为本地的H5格式文件。在使用Django开发Web网页端操作界面,实现用户上传一张鸟类图像,识别其名称。
60 12
鸟类识别系统Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+ResNet50算法模型+图像识别
|
13天前
|
机器学习/深度学习 算法 TensorFlow
交通标志识别系统Python+卷积神经网络算法+深度学习人工智能+TensorFlow模型训练+计算机课设项目+Django网页界面
交通标志识别系统。本系统使用Python作为主要编程语言,在交通标志图像识别功能实现中,基于TensorFlow搭建卷积神经网络算法模型,通过对收集到的58种常见的交通标志图像作为数据集,进行迭代训练最后得到一个识别精度较高的模型文件,然后保存为本地的h5格式文件。再使用Django开发Web网页端操作界面,实现用户上传一张交通标志图片,识别其名称。
43 6
交通标志识别系统Python+卷积神经网络算法+深度学习人工智能+TensorFlow模型训练+计算机课设项目+Django网页界面
|
9天前
|
机器学习/深度学习 人工智能 算法
【新闻文本分类识别系统】Python+卷积神经网络算法+人工智能+深度学习+计算机毕设项目+Django网页界面平台
文本分类识别系统。本系统使用Python作为主要开发语言,首先收集了10种中文文本数据集("体育类", "财经类", "房产类", "家居类", "教育类", "科技类", "时尚类", "时政类", "游戏类", "娱乐类"),然后基于TensorFlow搭建CNN卷积神经网络算法模型。通过对数据集进行多轮迭代训练,最后得到一个识别精度较高的模型,并保存为本地的h5格式。然后使用Django开发Web网页端操作界面,实现用户上传一段文本识别其所属的类别。
22 1
【新闻文本分类识别系统】Python+卷积神经网络算法+人工智能+深度学习+计算机毕设项目+Django网页界面平台
|
25天前
|
前端开发 搜索推荐 算法
中草药管理与推荐系统Python+Django网页界面+推荐算法+计算机课设系统+网站开发
中草药管理与推荐系统。本系统使用Python作为主要开发语言,前端使用HTML,CSS,BootStrap等技术和框架搭建前端界面,后端使用Django框架处理应用请求,使用Ajax等技术实现前后端的数据通信。实现了一个综合性的中草药管理与推荐平台。具体功能如下: - 系统分为普通用户和管理员两个角色 - 普通用户可以登录,注册、查看物品信息、收藏物品、发布评论、编辑个人信息、柱状图饼状图可视化物品信息、并依据用户注册时选择的标签进行推荐 和 根据用户对物品的评分 使用协同过滤推荐算法进行推荐 - 管理员可以在后台对用户和物品信息进行管理编辑
57 12
中草药管理与推荐系统Python+Django网页界面+推荐算法+计算机课设系统+网站开发
|
6天前
|
大数据 UED 开发者
实战演练:利用Python的Trie树优化搜索算法,性能飙升不是梦!
在数据密集型应用中,高效搜索算法至关重要。Trie树(前缀树/字典树)通过优化字符串处理和搜索效率成为理想选择。本文通过Python实战演示Trie树构建与应用,显著提升搜索性能。Trie树利用公共前缀减少查询时间,支持快速插入、删除和搜索。以下为简单示例代码,展示如何构建及使用Trie树进行搜索与前缀匹配,适用于自动补全、拼写检查等场景,助力提升应用性能与用户体验。
21 2
|
9天前
|
算法 Python
震惊!Python 算法设计背后,时间复杂度与空间复杂度的惊天秘密大起底!
在 Python 算法设计中,理解并巧妙运用时间复杂度和空间复杂度的知识,是实现高效、优雅代码的必经之路。通过不断地实践和优化,我们能够在这两个因素之间找到最佳的平衡点,创造出性能卓越的程序。
26 4
|
11天前
|
算法 搜索推荐 开发者
别再让复杂度拖你后腿!Python 算法设计与分析实战,教你如何精准评估与优化!
在 Python 编程中,算法的性能至关重要。本文将带您深入了解算法复杂度的概念,包括时间复杂度和空间复杂度。通过具体的例子,如冒泡排序算法 (`O(n^2)` 时间复杂度,`O(1)` 空间复杂度),我们将展示如何评估算法的性能。同时,我们还会介绍如何优化算法,例如使用 Python 的内置函数 `max` 来提高查找最大值的效率,或利用哈希表将查找时间从 `O(n)` 降至 `O(1)`。此外,还将介绍使用 `timeit` 模块等工具来评估算法性能的方法。通过不断实践,您将能更高效地优化 Python 程序。
27 4
|
8天前
|
算法 程序员 Python
程序员必看!Python复杂度分析全攻略,让你的算法设计既快又省内存!
在编程领域,Python以简洁的语法和强大的库支持成为众多程序员的首选语言。然而,性能优化仍是挑战。本文将带你深入了解Python算法的复杂度分析,从时间与空间复杂度入手,分享四大最佳实践:选择合适算法、优化实现、利用Python特性减少空间消耗及定期评估调整,助你写出高效且节省内存的代码,轻松应对各种编程挑战。
20 1
下一篇
无影云桌面