一、问题描述
给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
提示:
每个链表中的节点数在范围 [1, 100] 内
0 <= Node.val <= 9
题目数据保证列表表示的数字不含前导零
二、问题分析
在本题中,一个整数以链表的形式存储,且按个位逆序存储,因此两个整数相加可以直接将两个链表的对应节点的值相加。若链表长度不同,则可以将短链表用0补全,对齐长链表。
在相应节点值相加时,需要注意该位的进位情况。也就是说,两数之和的每一位由两个数对应位值n1,n2加上该位的进位c,即(n1 + n2 + c)%10,该位对下一位的进位为(n1 + n2 + c)/10。并在最后一位相加时应着重注意进位,若有进位应为进位新增一个节点。
具体代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
//知识点1:单向链表
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
//建立一个新的链表,其中包含一个头节点和一个尾节点
ListNode *head = nullptr, * tail = nullptr;
int c = 0,n1,n2,sum;
//将两个链表对应位相加,只要有一个链表该位有数即可加(链表名称即为头指针)
while(l1||l2){
//知识点2:条件运算符
//链表该位有值则赋值,无值则赋0
n1 = l1?l1->val:0;
n2 = l2?l2->val:0;
//对应位相加,记得加进位
sum = n1 + n2 + c;
//知识点3:动态内存
//判断此时是否为头节点,若是,则新节点同时替换为头节点和尾节点
if(!head)head = tail = new ListNode(sum%10);
else{
//若此时不是头节点,则将新节点添至末尾
tail->next = new ListNode(sum%10);
tail = tail->next;
}
c = sum / 10;
//移至下一个相加位;注:直接移动头指针很危险
if(l1)l1 = l1->next;
if(l2)l2 = l2->next;
}
//判断最高位是否有进位,若有则新增一个节点
if(c)tail->next = new ListNode(c);
//返回两数之和的新链表
return head;
};
时间复杂度为O(max(m,n)),m和n分别为两链表的长度。空间复杂度为O(1)。
三、知识点拓展
知识点1 单向链表
在介绍单向链表之前,简单介绍一下顺序表。顺序表同链表一样,都是线性表,他们线性存储元素。顺序表顺序固定,元素相邻,因此访问元素非常快速,但增删元素则十分麻烦,在顺序表中增删一个元素,将会影响到后面的所有元素。
因此,引出链表的概念。链表也按顺序存储元素,元素在逻辑上相邻,但在物理上不相邻,要使得在实际应用中找到与前一个元素逻辑上相邻而物理不相邻的元素,采用指针来解决这个问题。链表由节点组成,一个节点包括数据域和指针域,数据域保存值,指针域保存相邻节点的地址,对于单向链表,指针域仅包含指向下一个元素的指针。
单向链表的结构如上图所示。
名词解释:
头指针:指向第一个节点的指针变量。具有标识单向链表的作用,因此常用头指针代替链表名称。
头节点:存储具体元素的首元节点前的节点。可以不存储元素,也可以存储链表长度等信息。用于统一空链表和非空链表操作的节点。
*若不存在头节点,在非空链表中进行节点的增删,需要对前一个节点指向的下一个节点地址进行修改,并将指向尾节点的指针进行修改;而在空链表中,只需要将头指针指向的节点进行更改。两者操作不一致,因此需要头节点来进行操作统一。(however,实际应用中并不一定需要头节点,比如本题的解就没有使用kkk)
首元节点:存储第一个元素的节点。
知识点2 条件运算符
n1 = l1?l1->val:0;
条件?选项1:选项2;
若满足条件,则赋值选项1的值,否则赋值选项2的值。
参考文献:条件运算符
知识点3 动态内存
C++程序的内存分为栈、堆。栈内存是在声明变量时分配好的内存,堆内存是程序运行时动态分配的内存。
在不确定需要多少内存时,可使用特定运算符给指定类型变量分配堆内存,返回的是存储空间的地址。特定运算符如new,delete。
//分配double类型的动态内存
double *p = nullptr;
p = new double;
//释放内存
delete p;
参考文献:动态内存