- 题目描述:
You are given two linked lists representing two non-negative numbers.
The digits are stored in reverse order and each of their nodes contain a single digit.
Add the two numbers and return it as a linked list.
Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 0 -> 8
起初没看懂啥意思,后来百度了一下,又回过头看了一下题目描述,才知道题目是让干啥的了,也就是我上面标红的部分,其实这个题目是简化了的“大数相加”,从以下两个方面来降低了题目的难度:
- 链表,每一个node中存储的是一位,可以直接使用
- 给的链表是所要求和的书的倒序(reverse),即:2->4->3实际上所表示的是342,带上进位直接循环相加就行了
但是在处理的过程中,要注意以下问题:
- 两个链表的长度相同,并且相加所得的和的长度仍然不变,这是最简单的情形,记之为:ABC+DEF=GHI(注,这里的加数也是逆序的,与题目中描述的链表的形式是一致的,即ABC对应的真实的数据位CBA,下面没有特别说明的,都是按照这种形式),这种情况下,相加结束之后,即得到了最终的结果
- 两个链表的长度相同,但是相加所得的和的长度比原来的数的长度要长,这种情形较之刚才的稍微复杂一点,但还也不是特别复杂,记之为:ABC+DEF=GHIJ,这种情况下,相加结束之后,最终会剩余一个进位,这就需要在返回结果之前,还需要将进位添加到结果链表的尾部
- 两个链表的长度不同,这种情形下,又可以分出一下几种小情况:
- 较短部分相加之后,没有向上的进位,将两个加数记为ABC和DEFG,即ABC和DEF相加完之后,没有往上的进位,此时只需要将剩余的F位添加到结果链表的末尾即可
- 较短部分相加之后,有向上的进位,但是较长加数的剩余部分的第一位和进位相加后,没有再向上的后续进位,若将两个加数分别记为:AB和CDEF,即AB和CD向加之后,产生了一个向上的进位carry,此时CDEF中的E位要和carry相加,但是E和carry向加之后,没有进一步的进位,这种情况下,直接将E和carry相加的值重新赋值给E位,并将后续的位直接连接到结果链表中末尾就行了
- 较短部分相加之后,有向上的进位,并且较长加数的剩余部分的第一位和进位相加后,又产生了后续的进位,这种进位关系,直到后边的某一位才结束,若将两个加数分别记为:AB和CDEFGHI,即AD和CD相加之后,产生了一个向上的进位carry,此时CDEFGHI中E为和carry相加之后又产生了新的进位,这种进位关系直到后边的某一位才结束,这种情况下,除了要将EFGHI添加到结果链表外,还行要按序执行和carry的相加,直到某位加完之后,不再产生进位
直接上代码,先上一个C++的
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* pResultList = new ListNode(-1);
pResultList->next = NULL;
ListNode* p1 = l1;
ListNode* p2 = l2;
ListNode* pResultTailNode, *pResultNode;
int carry = 0, number = 0; //记录进位信息和结果(指mod 10 之后)
//初始化resultList
pResultTailNode = pResultList;
pResultNode = NULL;
while(NULL != p1 && NULL != p2)
{
number = (p1->val + p2->val + carry) % 10;
carry = (p1->val + p2->val + carry) / 10;
pResultTailNode->next = new ListNode(number);
pResultTailNode = pResultTailNode->next;
pResultTailNode->next = NULL;
p1 = p1->next;
p2 = p2->next;
}
if (NULL != p1)
{
pResultTailNode->next = p1;
while(NULL != p1)
{
pResultTailNode = pResultTailNode->next;
number = (p1->val + carry) % 10;
carry = (p1->val + carry) / 10;
p1->val = number;
p1 = p1->next;
}
}
if (NULL != p2)
{
pResultTailNode->next = p2;
while(NULL != p2)
{
pResultTailNode = pResultTailNode->next;
number = (p2->val + carry) % 10;
carry = (p2->val + carry) / 10;
p2->val = number;
p2 = p2->next;
}
}
if (0 != carry)
{
pResultTailNode->next = new ListNode(carry);
pResultTailNode = pResultTailNode->next;
pResultTailNode->next = NULL;
}
pResultTailNode = pResultList->next;
delete pResultList;
pResultList = pResultTailNode;
return pResultList;
}
};
该代码中,在相加完公共部分后,处理剩余部分的时候(即在if条件语句总的while判断条件处),只是在p1(或者p2)不为NULL为结束条件,不论carry是否为0,一直到结束,这会耗费时间,提交上之后,统计结果提示用了44ms
刚刚又修改了一下,只是在第41和第54行处的while循环条件上加了对carry的判断,因为当carry为0时,就不需要再往后遍历执行了,
if (NULL != p1)
{
pResultTailNode->next = p1;
<span style="background-color: rgb(255, 0, 0);">while(NULL != p1 && 0 != carry)</span>
{
pResultTailNode = pResultTailNode->next;
number = (p1->val + carry) % 10;
carry = (p1->val + carry) / 10;
p1->val = number;
p1 = p1->next;
}
}
if (NULL != p2)
{
pResultTailNode->next = p2;
<span style="background-color: rgb(255, 0, 0);">while(NULL != p2 && 0 != carry)</span>
{
pResultTailNode = pResultTailNode->next;
number = (p2->val + carry) % 10;
carry = (p2->val + carry) / 10;
p2->val = number;
p2 = p2->next;
}
}
这时,又看了下Details,发现运行时间减少了8ms,但是距离C++代码中的最优代码,还有一点点差距,
后来又写了一个C版本的,与C++版本的相比,执行时间要减少了一半,直接贴代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) {
struct ListNode* pResultList = (struct ListNode*)malloc(sizeof(struct ListNode));
pResultList->next = NULL;
struct ListNode* p1 = l1;
struct ListNode* p2 = l2;
struct ListNode* pResultTailNode, *pResultNode;
int carry = 0, number = 0; //记录进位信息和结果(指mod 10 之后)
//初始化resultList
pResultTailNode = pResultList;
pResultNode = NULL;
while(NULL != p1 && NULL != p2)
{
number = (p1->val + p2->val + carry) % 10;
carry = (p1->val + p2->val + carry) / 10;
pResultTailNode->next = (struct ListNode *)malloc(sizeof(struct ListNode));
pResultTailNode = pResultTailNode->next;
pResultTailNode->val = number;
pResultTailNode->next = NULL;
p1 = p1->next;
p2 = p2->next;
}
if (NULL != p1)
{
pResultTailNode->next = p1;
while(NULL != p1)
{
pResultTailNode = pResultTailNode->next;
number = (p1->val + carry) % 10;
carry = (p1->val + carry) / 10;
p1->val = number;
p1 = p1->next;
}
}
if (NULL != p2)
{
pResultTailNode->next = p2;
while(NULL != p2)
{
pResultTailNode = pResultTailNode->next;
number = (p2->val + carry) % 10;
carry = (p2->val + carry) / 10;
p2->val = number;
p2 = p2->next;
}
}
if (0 != carry)
{
pResultTailNode->next = (struct ListNode *)malloc(sizeof(struct ListNode));
pResultTailNode = pResultTailNode->next;
pResultTailNode->val = carry;
pResultTailNode->next = NULL;
}
pResultTailNode = pResultList->next;
free(pResultList);
pResultList = pResultTailNode;
return pResultList;
}
与C++版本的类似,在while循环中,如果加上对carry的判断,执行时间会减少2ms,
if (NULL != p1)
{
pResultTailNode->next = p1;
while(NULL != p1 && 0 != carry)
{
pResultTailNode = pResultTailNode->next;
number = (p1->val + carry) % 10;
carry = (p1->val + carry) / 10;
p1->val = number;
p1 = p1->next;
}
}
if (NULL != p2)
{
pResultTailNode->next = p2;
while(NULL != p2 && 0 != carry)
{
pResultTailNode = pResultTailNode->next;
number = (p2->val + carry) % 10;
carry = (p2->val + carry) / 10;
p2->val = number;
p2 = p2->next;
}
}
details
注:关于代码最后的三句:
pResultTailNode = pResultList->next;
delete pResultList;
pResultList = pResultTailNode;
至于为何要添加这三句,链表有带头结点的链表和不带头结点的链表,而此处我是将其看做不带头结点的链表来处理的。
有更近一步优化的,还望进一步交流~~