每天一道算法题-两数相加

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

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

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

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

示例二:
输入:l1 = [0], l2 = [0]
输出:[0]

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

首先,题目说两个非空链表,每个节点存一位数字,而且是逆序存储的。比如,数字342会被存为2->4->3这样的链表。那我要把这样的两个链表相加,得到的结果同样用逆序的链表表示。并且,除了数字0之外,这两个数都不会以0开头。那输出的链表也应该符合这个条件,比如结果如果是0的话,就只能有一个节点。

那怎么做呢?我记得通常处理这种链表加法的时候,是模拟加法进位的过程。因为链表是逆序的,所以最低位在链表头,这样可以直接从头开始相加,处理进位。这应该比较直接。

那步骤大概是这样的:同时遍历两个链表,逐位相加,同时考虑进位。当其中一个链表遍历完时,继续处理剩下的节点,加上进位。最后如果还有进位,要新建一个节点。

比如例子中的情况:比如输入是2->4->3(342)和5->6->4(465),相加得到807,结果应该是7->0->8。那链表就是7->0->8。这个过程需要处理每一位相加,进位保存下来加到下一位。

那具体实现的话,我需要创建两个指针分别遍历两个链表,同时维护一个进位变量carry。每次取出两个节点的值相加,加上carry,然后计算当前位的值(sum%10)和新的进位(sum/10)。然后将当前位的值作为新节点添加到结果链表中。直到两个链表都遍历完,并且进位为0为止。

这里需要注意的情况是,当两个链表长度不一致的时候,较长的那个链表剩下的部分需要继续处理。比如一个链表已经遍历完,另一个还有节点,或者进位还存在的情况下要继续处理。

举个例子,比如链表1是9->9,链表2是9。那相加的话,结果应该是8->0->1。即99+9=108,逆序的话是8->0->1。这时候,当链表2遍历完后,链表1还有节点,这时候要继续处理剩下的节点,加上进位。

那如何实现呢?可以用循环,当链表1或链表2还有节点,或者进位不为零的时候继续循环。在循环中,每次取出当前节点的值,如果链表已经到头了,就取0。然后计算和,然后移动指针,如果链表还有下一个节点的话。

那现在考虑链表的遍历方式。比如,用while循环,条件是l1不为空或者l2不为空或者carry不为零。这样就能覆盖所有情况。

比如,初始化进位carry为0。然后,在每次循环中:

• 取l1的当前值,如果l1不为空,否则取0。
• 取l2的当前值,如果l2不为空,否则取0。
• 计算sum = val1 + val2 + carry。
• 计算新的carry = sum / 10。
• 创建新节点,值为sum %10。
• 将新节点连接到结果链表的末尾。
• 移动l1和l2的指针(如果存在的话)。

但是,这样处理的话,结果链表的构建需要维护一个当前指针,或者使用哑结点(dummy node)来简化操作。哑结点的下一个节点是真正的头节点,这样可以避免处理初始空链表的情况。

比如,初始化一个dummy节点,然后维护一个当前指针curr,初始指向dummy。每次生成新节点时,curr.next指向它,然后curr移动到下一个节点。这样最后返回dummy.next即可。

这样就能处理所有情况,包括最后的进位。例如,当所有节点都处理完了,但还有进位1,这时候需要新建一个节点。

那现在编写具体的代码步骤:

  1. 创建一个哑结点dummy,curr指向dummy。
  2. 初始化carry为0。
  3. 当l1不为空,或l2不为空,或者carry不为0时循环:
    a. 获取l1的值,如果l1存在,否则为0。
    b. 获取l2的值,如果l2存在,否则为0。
    c. 计算sum = val1 + val2 + carry。
    d. 计算新的carry = sum /10。
    e. 创建新节点,值为sum %10。
    f. curr.next指向新节点,curr移动到next。
    g. 如果l1不为空,l1移动到l1.next。
    h. 如果l2不为空,l2移动到l2.next。
  4. 返回dummy.next作为结果链表的头节点。

那这样的逻辑应该可以覆盖所有情况。

比如测试案例:

例子1:
输入:l1 = [2,4,3], l2 = [5,6,4]
相加结果:342 + 465 = 807 → 逆序是7->0->8。
按照步骤:
初始化carry为0。
第一次循环:
val1=2,val2=5,sum=7+0=7。carry=0,新节点7。curr指向它。l1和l2都移动到下一个。
第二次循环:
val1=4,val2=6,sum=10+0=10。sum%10=0,carry=1。新节点0。curr指向它。l1和l2移动到下一位。
第三次循环:
val1=3,val2=4,sum=3+4+1=8 → sum%10=8,carry=0。新节点8。此时l1和l2都到头了,但循环继续吗?因为此时carry为0,但是此时循环的条件是l1或l2不为空或carry不为零。此时l1和l2都是空,carry为0,所以退出循环。
结果链表是7->0->8。正确。

另一个例子,其中一个链表较长:
比如l1是9->9->9,l2是9->9。即数字是999和99,相加是1098。逆序存储的话,链表应该是8->9->0->1。
处理过程:
初始carry=0。
第一次循环:9+9=18+0=18 → sum%10=8,carry=1。新节点8。l1和l2都移动。
第二次循环:9+9+1=19 → sum%10=9,carry=1。l1移动,l2为空。
第三次循环:9+0+1=10 → sum%10=0,carry=1。此时l1还有吗?l1此时是第三个节点,处理完这次后,l1变为null。l2还是null,但carry是1。所以循环继续。
第四次循环:0+0+1=1 → sum%10=1,carry=0。创建节点1。此时所有条件都不满足,退出循环。结果链表是8->9->0->1。正确。

那这样处理是正确的。

那关于哑结点的使用,这样可以避免处理初始头节点为空的情况,简化代码。

现在考虑代码的实现,比如用Java的话:

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode curr = dummy;
int carry = 0;
while (l1 != null || l2 != null || carry != 0) {
int val1 = (l1 != null) ? l1.val : 0;
int val2 = (l2 != null) ? l2.val : 0;
int sum = val1 + val2 + carry;
carry = sum / 10;
curr.next = new ListNode(sum % 10);
curr = curr.next;
if (l1 != null) l1 = l1.next;
if (l2 != null) l2 = l2.next;
}
return dummy.next;
}

这样应该可以正确处理所有情况。例如,当两个链表都处理完,但还有进位的情况,比如最后进位为1时,会生成一个新节点。

那这个解法的时间复杂度是O(max(m, n)),其中m和n是两个链表的长度,因为需要遍历两个链表的全部节点。空间复杂度是O(max(m, n)),因为需要创建结果链表,其长度最多为max(m,n)+1(比如最后进位的情况)。

那有没有特殊情况需要处理?比如两个链表都为空,但题目规定是非空的,所以不需要考虑。或者结果链表的前导零?题目说明除了数字0外,不会以0开头,所以比如结果为0的情况只能是0本身,也就是一个节点。这种情况会在循环中处理吗?

比如,输入是两个0的链表。则相加得到0,此时循环的条件是l1或l2不为空或carry不为零。初始时,l1和l2都是非空,所以进入循环:

val1=0,val2=0,sum=0+0+0=0,carry=0。创建节点0。然后l1和l2都移动到null。此时循环是否继续?此时,l1和l2都为空,carry为0,所以不进入循环。返回dummy.next是节点0。正确。

那没有问题。

另一个测试案例,比如l1是[9,9],l2是[9]。相加得到18 + 9 = 99+9=108?或者原题中的例子是否应该更仔细?

比如,链表1是9->9,即99,逆序的话是99。链表2是9,即9。相加得到99+9=108。逆序存储的话应该是8->0->1。按照上述代码的处理:

第一次循环,sum是9+9+0=18 → sum%10=8,carry=1。节点8。l1移动,l2移动(到null)。
第二次循环,l1有节点9,val1=9,val2=0,sum=9+0+1=10 → sum%10=0,carry=1。节点0。l1移动(null)。
第三次循环,l1和l2都null,carry=1。sum=0+0+1=1 → sum%10=1,carry=0。节点1。此时循环结束。结果链表是8->0->1,正确。

综上,这个代码应该可以正确处理所有情况。

要将两个逆序链表表示的数字相加并返回同样格式的链表,可以按照以下步骤进行:

方法思路

  1. 初始化:使用哑结点(dummy node)简化链表操作,并维护一个当前指针 curr 来构建结果链表。同时,用变量 carry 记录进位值。
  2. 遍历链表:同时遍历两个链表,逐位相加:
    • 如果某个链表已遍历完,则其后续位的值视为0。
    • 计算当前位的和(包括进位),更新进位。
    • 将当前位的值作为新节点添加到结果链表中。
  3. 处理剩余进位:若遍历结束后仍有进位,需添加新节点。

解决代码

class ListNode {int val;ListNode next;ListNode() {}ListNode(int val) { this.val = val; }ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {ListNode dummy = new ListNode(0);ListNode curr = dummy;int carry = 0;while (l1 != null || l2 != null || carry != 0) {int val1 = (l1 != null) ? l1.val : 0;int val2 = (l2 != null) ? l2.val : 0;int sum = val1 + val2 + carry;carry = sum / 10;curr.next = new ListNode(sum % 10);curr = curr.next;if (l1 != null) l1 = l1.next;if (l2 != null) l2 = l2.next;}return dummy.next;}
}

代码解释

  1. 哑结点初始化dummy 节点简化链表头部的处理,curr 用于逐步构建结果链表。
  2. 循环条件l1l2 未遍历完,或仍有进位时继续循环。
  3. 计算当前位和进位:取两链表当前节点的值(若存在),加上进位计算总和,更新进位。
  4. 添加新节点:将当前位的值(sum % 10)作为新节点,移动 curr
  5. 移动链表指针:若链表未遍历完,继续移动指针。
  6. 返回结果:最终返回哑结点的下一个节点,即结果链表的头节点。

该方法时间复杂度为 O(max(m, n)),空间复杂度为 O(max(m, n)),能高效处理两数相加问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/36917.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络华为HCIA+HCIP 广域网技术

目录 PPP协议 PPP链路建立流程 PPP链路接口状态机 LCP报文格式 LCP协商过程-正常协商 LCP协商过程-参数不匹配(MRU) LCP协商过程-参数不识别 PPP认证模式 - PAP PPP认证模式 - CHAP NCP协商 - 静态IP地址协商 NCP协商 - 动态IP地址协商 P…

B站pwn教程笔记-5

复习和回顾 首先复习一下ELF文件在内存和磁盘中的不同。内存只关注读写这权限,会合并一些代码段。 动态链接库只在内存中单独装在一份 因为很多软件都要用动态链接库了,不可能一个个单独复制一份。但是在有的调试环境下会单独显示出来各一份。 ld.so是装…

Vue Router 的核心实现原理是什么?

文章目录 一、路由模式实现原理1. Hash 模式2. History 模式 二、响应式路由系统1. 路由对象响应化2. 路由映射解析 三、组件渲染机制1. RouterView 实现2. 路由匹配流程 四、导航守卫系统1. 守卫执行流程2. 守卫类型对比 五、核心源码结构六、设计亮点分析七、性能优化策略总结…

CherryStudio + 火山引擎DeepSeek R1 告别服务器繁忙

CherryStudio 火山引擎DeepSeek R1 告别服务器繁忙 一、下载CherryStudio并安装 CherryStudio是功能强大的多模型桌面客户端,支持Windows、macOS和Linux系统。集成了多种主流的大语言模型(如OpenAI、DeepSeek、Gemini等)以及本地模型运行功…

Hessian 矩阵是什么

Hessian 矩阵是什么 目录 Hessian 矩阵是什么Hessian 矩阵的性质及举例说明**1. 对称性****2. 正定性决定极值类型****特征值为 2(正),因此原点 ( 0 , 0 ) (0, 0) (0,0) 是极小值点。****3. 牛顿法中的应用****4. 特征值与曲率方向****5. 机器学习中的实际意义**一、定义与…

C#从入门到精通(1)

目录 第一章 C#与VS介绍 第二章 第一个C#程序 (1)C#程序基本组成 1.命名空间 2.类 3.Main方法 4.注释 5.语句 6.标识符及关键字 (2)程序编写规范 1.代码编写规则 2.程序命名方法 3.元素命名规范 第三章 变量 &…

【LINUX操作系统】 动静态库的链接原理

初识linux(16) 动静态库(手搓动静态库!)-CSDN博客 完成了对动静态库使用的学习,现在浅显理解下动态库加载的原理。 1. 宏观认知 磁盘中的应用程序main和动态库libmystdio.so先加载到内存中 加载到内存后&am…

广东启动“跨境电商+产业带”系列活动 三年打造30个产业振兴样板

大湾区经济网湾区财经快讯,近日,2025年广东省“跨境电商+产业带”助力“百千万工程”系列活动在中山市古镇镇启动。作为外贸领域新质生产力的重要载体,跨境电商将通过赋能县域特色产业带转型升级,为城乡融合与乡村振兴…

穿透递归的本质:从无限梦境到可控魔法的蜕变之路

穿透递归的本质:从无限梦境到可控魔法的蜕变之路(C实现) 一、递归:程序员的盗梦空间 在计算机科学的宇宙中,递归是最接近魔法本质的编程范式。它像一面镜子中的镜子,引导我们通过自我相似性破解复杂问题。…

基于django+vue的购物商城系统

开发语言:Python框架:djangoPython版本:python3.8数据库:mysql 5.7数据库工具:Navicat11开发软件:PyCharm 系统展示 系统首页 热卖商品 优惠资讯 个人中心 后台登录 管理员功能界面 用户管理 商品分类管理…

WordPress靶场攻略

后台修改模板 修改404.php为一句话木马 访问404.php,验证有没有成功 http://47.122.51.245:8080/wp-content/themes/twentyfifteen/404.php?cmdphpinfo(); 上传主题 创建6.php,写入图中代码 自己随便下载一个主题包,将1.php和主题包压缩在一起,提交上…

JVM常用概念之对象对齐

问题 对象对齐有什么规范吗?对象对齐是8个字节吗? 基础知识 许多硬件实现要求对数据的访问是对齐的,即确保所有 N 字节宽度的访问都在 N 的整数倍的地址上完成。即使对于普通的数据访问没有特别要求,特殊操作(特别是原子操作&#xff09…

K8S学习之基础三十七:prometheus监控node资源

Prometheus v2.2.1 ​ 编写yaml文件,包含创建ns、configmap、deployment、service # 创建monitoring空间 vi prometheus-ns.yaml apiVersion: v1 kind: Namespace metadata:name: monitor-sa# 创建SA并绑定权限 kubectl create serviceaccount monitor -n monito…

leetcode127.单词接龙

本题的思路就是将所有可转换的序列相连,构成图,然后选择起始词作为广度优先遍历的起点,那么就能找到转换的最小步骤数 而这里的两个单词是否相连不是真的把他们弄成一张图,而是采用暴力枚举,逐个尝试替换字母&#xf…

Tr0ll2靶机详解

一、主机发现 arp-scan -l靶机ip:192.168.55.164 二、端口扫描、漏洞扫描、目录枚举、指纹识别 2.1端口扫描 nmap --min-rate 10000 -p- 192.168.55.164发现21端口的ftp服务开启 以UDP协议进行扫描 使用参数-sU进行UDP扫描 nmap -sU --min-rate 10000 -p- 19…

Pycharm接入DeepSeek,提升自动化脚本的写作效率

一.效果展示: 二.实施步骤: 1.DeepSeek官网创建API key: 创建成功后,会生成一个API key: 2. PyCharm工具,打开文件->设置->插件,搜索“Continue”,点击安装 3.安装完成后&…

如何在 Node.js 中使用 .env 文件管理环境变量 ?

Node.js 应用程序通常依赖于环境变量来管理敏感信息或配置设置。.env 文件已经成为一种流行的本地管理这些变量的方法,而无需在代码存储库中公开它们。本文将探讨 .env 文件为什么重要,以及如何在 Node.js 应用程序中有效的使用它。 为什么使用 .env 文…

《视觉SLAM十四讲》ch13 设计SLAM系统 相机轨迹实现

前言 相信大家在slam学习中,一定会遇到slam系统的性能评估问题。虽然有EVO这样的开源评估工具,我们也需要自己了解系统生成的trajectory.txt的含义,方便我们更好的理解相机的运行跟踪过程。 项目配置如下: 数据解读: …

软考高级信息系统管理工程师通关100题(21-40)附记忆口诀

文章目录 21.常用存储模式的技术与应用对比22.物联网架构23.云计算服务提供的资源层次24.大数据25.区块链26.人工智能27.虚拟现实VR28.IT治理的内涵29.IT 治理活动30.IT治理本质31.IT审计目标32.IT审计方法33.治理系统设计34.数据管理能力成熟度评估模型35.项目管理原则36.管理…