题目解析
给定两个用链表表示的整数,每个节点包含一个数位。这些数位是反向存放的,也就是个位排在链表首部。编写函数对这两个整数求和,并用链表形式返回结果。
题目传送门:面试题 02.05. 链表求和
示例:
输入:(7 -> 1 -> 6) + (5 -> 9 -> 2),即617 + 295
输出:2 -> 1 -> 9,即912
示例:
输入:(6 -> 1 -> 7) + (2 -> 9 -> 5),即617 + 295
输出:9 -> 1 -> 2,即912
进阶:
- 思考一下,假设这些数位是正向存放的,又该如何解决呢?
解题思路与代码
这道题不算一道太难的链表题,主要还是考验了一下你如何构建一条新链表的操作。基本的逻辑也比较简单。你需要去保存一个进位的变量,你需要满十进一。之后就是创建链表的操作啦。
当然你也可以选择去在原链表上去进行操作。但是我觉得那样子指针指的可能会比较乱。我还是选择创建一条新的链表吧。
方案一:遇十进一,创建新链表
就像刚刚说的那样,我们来实现我们的代码:
具体的代码如下:
/**
* 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) {
if(l1 == nullptr) return l2;
if(l2 == nullptr) return l1;
int carry = 0;
ListNode * newList = new ListNode(0);
ListNode * newHead = newList;
while(l1 != nullptr && l2 != nullptr){
int sum = l1->val + l2->val + carry;
ListNode * temp = new ListNode(sum % 10);
carry = sum / 10;
l1 = l1->next;
l2 = l2->next;
newHead->next = temp;
newHead = temp;
}
while(l1 != nullptr){
int sum = carry + l1->val;
ListNode * temp = new ListNode(sum % 10);
carry = sum / 10;
newHead->next = temp;
newHead = temp;
l1 = l1->next;
}
while(l2 != nullptr){
int sum = carry + l2->val;
ListNode * temp = new ListNode(sum % 10);
carry = sum / 10;
newHead->next = temp;
newHead = temp;
l2 = l2->next;
}
if(carry > 0){
ListNode * temp = new ListNode(carry);
newHead->next = temp;
}
return newList->next;
}
};
复杂度分析:
时间复杂度:
- 我们首先遍历了两个链表,这需要的时间是O(n + m),其中n和m分别代表了两个链表的长度。然后你又分别处理了l1和l2剩余的部分,所以总的时间复杂度为O(n + m),这是线性的时间复杂度。
空间复杂度:
- 你创建了一个新的链表来存储结果,这需要的空间是O(max(n, m)),因为在最坏的情况下,新链表的长度是最长的输入链表的长度。所以这个代码的空间复杂度是O(max(n, m))。
方案二:优化!使代码看起来更加整洁
根据刚刚写的代码,我发现我不需要那么多的while循环,我只需要一个while循环就行了,整体优化了一下代码结构,使代码看起来更简洁,也更简单。
具体的代码如下:
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode * head = nullptr;
ListNode * curr = nullptr;
int carry = 0;
while(l1 || l2){
int v1 = l1 ? l1->val : 0;
int v2 = l2 ? l2->val : 0;
int sum = v1 + v2 + carry;
if(!head) head = curr = new ListNode(sum % 10);
else{
curr->next = new ListNode(sum % 10);
curr = curr->next;
}
carry = sum / 10;
if(l1) l1 = l1->next;
if(l2) l2 = l2->next;
}
if(carry) curr->next = new ListNode(carry);
return head;
}
};
复杂度分析
因为只是优化了一下代码结构,所以时间复杂度与空间复杂度完全没有变化。
进阶:思考一下,假设这些数位是正向存放的,又该如何解决呢?
-
先开个玩笑,其实可以先翻转链表,然后再进行同为相加操作,hh。这种写法我就不写了,没什么必要。
-
那换一种思路想想,有没有什么数据结构,可以让我们实现先加链表的最后面的节点呢?
答案是:栈(stack) 。我们可以分别把链表l1,l2的节点分别压入栈中。此时取出来的元素必定是相同位数。如果有的栈先取完了,和刚刚一样,当做0就行。 -
注意,虽然说,这里的进阶是让你去思考,如果你拿我的代码去leetcode上去提交,题解肯定是不通过的,这是因为,leetcode里面的案例都是个位数是排在前面的,而我们这个进阶思考是让你将最高位排在最前面。
-
而将最高位排在最前面其实就运用到了一个技巧,就是用循环来插入头节点。也就是反转链表
具体的代码如下:
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
stack<int> s1, s2;
while (l1) {
s1.push(l1 -> val);
l1 = l1 -> next;
}
while (l2) {
s2.push(l2 -> val);
l2 = l2 -> next;
}
int carry = 0;
ListNode* head = nullptr;
while (!s1.empty() or !s2.empty() or carry != 0) {
int a = s1.empty() ? 0 : s1.top();
int b = s2.empty() ? 0 : s2.top();
if (!s1.empty()) s1.pop();
if (!s2.empty()) s2.pop();
int cur = a + b + carry;
carry = cur / 10;
cur %= 10;
auto pos = new ListNode(cur);
pos -> next = head;
head = pos;
}
return head;
}
};
总结
这道题的意义在于以下几点:
-
链表操作:这道题目考察了对链表的基本操作,包括创建新节点,遍历链表,以及处理链表节点之间的链接关系。
-
数据结构的选择:在不同的数据表示下,相同的问题可能需要不同的解决策略。例如,这个问题中,数位是反向存放的,这样我们可以直接从链表的头部开始操作,方便处理进位问题。但在进阶问题中,数位是正向存放的,我们就需要将链表反转或者使用栈等数据结构,才能便捷地处理进位问题。
-
大数运算:这道题目也间接涉及到了大数运算的问题。在实际的编程中,我们可能会遇到超出基本数据类型表示范围的大数,这时候就需要用链表、数组或者字符串等方式来表示这些大数,然后实现基本的数学运算,例如这个问题中的加法运算。
-
递归与迭代思想:在解决这个问题的时候,我们可以用迭代的方式,也可以用递归的方式。这都考察了我们对这两种基本编程思想的理解。
总的来说,这道题目在帮助我们锻炼基本的链表操作技巧的同时,也提高了我们对数据结构选择,大数运算,以及递归与迭代思想的理解。
最后的最后,如果你觉得我的这篇文章写的不错的话,请给我一个赞与收藏,关注我,我会继续给大家带来更多更优质的干货内容
。