0.问题重述
给定两个非空链表来表示两个非负整数。位数按照逆序方式存储,它们的每个节点只存储单个数字。将两数相加返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
1.问题分析
题目将所有的数据储存为链表,实际上是简化了计算,并且本身是逆序存储,符合从个位到十位再往上的加法原理,进位也比较好处理。所以就对两个链表进行遍历,新的链表储存对应位置的和。设置一个bool值,如果进位就将bool值更新。若是某个链表最终遍历结束,而另一个链表还未结束,就将多出来的部分全部加进去。
2.代码
class Solution {
public:
ListNode * addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode *head = new ListNode(0);
ListNode *p = head;
ListNode *res = nullptr;
bool flag = true;
while (l1&&l2) {
int t = (l1->val + l2->val);
if (flag == false) {
t++;
flag = true;
}
if (t >= 10) {
flag = false;
}
res = new ListNode(t % 10);
p->next = res;
p = res;
l1 = l1->next;
l2 = l2->next;
}
if (!l1 && !l2) {
if (flag == false) {
res = new ListNode(1);
p->next = res;
p = res;
}
p->next = nullptr;
head = head->next;
return head;
}
if (!l1) {
while (l2) {
int t = l2->val;
if (flag == false) {
t++;
flag = true;
}
if (t >= 10) {
flag = false;
}
res = new ListNode(t%10);
p->next = res;
p = res;
l2 = l2->next;
}
if (flag == false) {
res = new ListNode(1);
p->next = res;
p = res;
}
p->next = nullptr;
head = head->next;
return head;
}
if (!l2) {
while (l1) {
int t = l1->val;
if (flag == false) {
t++;
flag = true;
}
if (t >= 10) {
flag = false;
}
res = new ListNode(t%10);
p->next = res;
p = res;
l1 = l1->next;
}
if (flag == false) {
res = new ListNode(1);
p->next = res;
p = res;
}
p->next = nullptr;
head = head->next;
return head;
}
return head;
}
};
在写代码的时候遇到了如下几个问题:
- 没有考虑两个长度相同链表的进位问题。比如说5+5=10,在链表遍历完成后应该再判断一次进位。
- 没有考虑两个不同长度的链表多次进位问题。比如说1+99,在单独对第二个链表进行操作的时候仍然进行了两次进位。
- 多余考虑了0的情况。原来的算法在一开始对首位开始为0进行了判断,如果是0,直接返回另一个链表。但这实际上是不对的,因为有可能是1230的情况。另外,即使0在开头,也不用去额外判断,因为0对加法没有影响。所以无需额外进行0的判断。
3.代码优化
看了leetcode的官方解答也是采取的这种思路,但是网上其他的解答有的就精简许多。基本上只有了1/4的代码量…
我还是太vegetable了。
class Solution {
public:
ListNode * addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode *head = new ListNode(0);
ListNode *p = head;
int sum = 0;
while (l1 || l2) {
if (l1) {
sum += l1->val;
l1 = l1->next;
}
if (l2) {
sum += l2->val;
l2 = l2->next;
}
p->next = new ListNode(sum % 10);
sum /= 10;
p = p->next;
}
if (sum) {
p->next= new ListNode(1);
}
return head->next;
}
};
他将两个链表判空放在一起,不管哪个空了都无所谓,反正sum的处理都在最后。并且只用两个指针,一个头指针,一个是下一位的指针。
所有相同的部件都可以拿出来共同使用
复杂度分析
时间复杂度:O(\max(m, n))O(max(m,n)),假设 mm 和 nn 分别表示 l1l1 和 l2l2 的长度,上面的算法最多重复 \max(m, n)max(m,n) 次。
空间复杂度:O(\max(m, n))O(max(m,n)), 新列表的长度最多为 \max(m,n) + 1max(m,n)+1。
4.Java解法
Java解法的思路与C++一致。算法上并无差别。
但是仔细思考后还是存在一些问题,因为我开始学习的语言是C++,指针在其中占据了非常重要的位置。特别是在链表相关的题目里,但是Java包括之后我们要讨论的Python,都不存在指针,那么对于这两种语言而言,是怎么处理链表这种数据结构的呢?
实际上Java内部,除了基本的数据类型外,均为引用,栈变量指向了堆里的变量,本身可理解为指针。我们可以看到有时在新建一个数据的时候,本身采用的代码就是:
ListNode next = new ListNode(0);
int[] a=new int[10];
实际上就是堆上有一个数据实例,栈上的变量指向了该实例。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode result = new ListNode(0);
ListNode first = l1;
ListNode second = l2;
int val,rest = 0;
boolean isHead = true;
ListNode tmp = result;
while(null != first && null != second) {
val = first.val + second.val + rest;
if(isHead) {
result.val = val % 10;
isHead = false;
} else {
ListNode next = new ListNode(val % 10);
tmp.next = next;
tmp = next;
}
rest = val / 10;
first = first.next;
second = second.next;
}
// 到这里,first和second至少有一个为null
if(null != first || null != second) {
ListNode t = null != first ? first : second;
tmp.next = addTwoNumbers(new ListNode(rest),t); // 递归执行剩下的
} else {
// 都为空
if(0 != rest) {
tmp.next = new ListNode(rest);
}
}
return result;
}
}
5.Python解法
注意在写的时候还是没考虑到一个条件,最后的判断条件不能是首数字为0,因为有可能没有进位,应该直接设置一个进位符,根据进位符进行判断。
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
head=ListNode(0)
res=head
c=0
while (l1||l2):
x=l1.val if l1 else 0
y=l2.val if l2 else 0
s=x+y+c
c=s//10;
res.next=ListNode(s%10)
res=res.next
if(l1.next!=None):l1.next
if(l2.next!=None):l2.next
if (c>0) :
res.next=ListNode(1)
return head->next