分区:链表
2.两数相加
题目描述:
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
(来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/add-two-numbers)
思路与算法
可以看出,两个输入的链表是逆序的,因此两个链表中同一位置的数字可以直接相加。
需要避坑的点:
1、如果两个链表的长度不一样,那对应位置的值该如何加减;
2、对应位置的进位值应该如何处理?我当时想的是判断这轮有没有进位,如果有的话就把链表L1的下一位值通过指针指向val来自加一,但后来发现这样的话当L1加到最后一位这种思路就没办法处理。
3、如何构建两数之和的链表?把对应位置相加之和的值赋给L1(L2也行)的对应位置?还是另外构建一个链表,每次得到对应位置的和之后构建新的结点,然后把该节点连接到新的结点上?答案显然是后者,因为按照前一种方案来的话,按照循环进行操作时,每轮执行的操作应该是相同的,但是L1的长度可能和L2不同,那样的话在L1长度不足的时候就需另外构建结点来保存对应位置的值,然后再接到L1后面,但是这样的操作显然在循环过程中无法格外进行。
解决方案
针对避坑点1:用两个变量n1和n2在每轮循环时分别存储对应位置的值,并且如果在这轮循环中L1为空,但是L2还有值的话,可以利用条件运算符(?:)来使n1为0,这样就可以保证每轮对应位置都有数相加。
int n1 = l1 ? l1->val: 0;
int n2 = l2 ? l2->val: 0;
对于避坑点2:可以用一个变量carry来保存每轮相加的进位值,初始时令carry为0,但每轮循环结束就保存了这轮的进位值,然后作用到下一轮的两数相加中。
//本轮循环
sum = n1+n2+carry;
//......其他操作
carry = sum/10; //用取余操作得到进位值并保存,然后作用于下一轮循环
对于避坑点3:我想的是建立一个链表(带头结点)之后,每次都在循环内用尾插法(常用,最近在复习考研)建立两数值和的单链表,并在返回之前删除头结点,不然不能AC(Accept)。这样的话就需要一个尾指针r来保存最后的结点位置,会耗费空间,但这是我这个本人看了答案之后总结出对自己比较友好记忆的方法,只能这样理解了。而且力扣给的官方解答比较巧妙,相信在比赛的时候我是不会想到的。
ListNode* head=new ListNode(-1); //C++语言建立头结点
ListNode *temp=nullptr,*r=head; //temp是临时结点,在循环中构建结点用的,r是尾指针,初始时指向头结点
//......
//将结点插入两数之和的聊表中的操作
temp = new ListNode(sum%10);
r->next=temp;
r=temp;
下面给出官方解答和我的本办法:
官方解答:
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode *head = nullptr, *tail = nullptr;
int carry = 0;
while (l1 || l2) {
int n1 = l1 ? l1->val: 0;
int n2 = l2 ? l2->val: 0;
int sum = n1 + n2 + carry;
if (!head) {
head = tail = new ListNode(sum % 10);
} else {
tail->next = new ListNode(sum % 10);
tail = tail->next;
}
carry = sum / 10;
if (l1) {
l1 = l1->next;
}
if (l2) {
l2 = l2->next;
}
}
if (carry > 0) {
tail->next = new ListNode(carry);
}
return head;
}
};
链接:https://leetcode-cn.com/problems/add-two-numbers/solution/liang-shu-xiang-jia-by-leetcode-solution/(求生欲)
我的笨办法:
/**
* 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:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* head=new ListNode(-1); //建立头结点,构建两数之和的链表
ListNode *temp=nullptr,*r=head;
int carry = 0;
int n1,n2;
int sum=0;
while(l1||l2){ //循环对每轮位置的数相加
n1=l1?l1->val:0; //这种对应位置的处理很巧妙,很多情况都能用到
n2=l2?l2->val:0;
sum = n1+n2+carry;
temp = new ListNode(sum%10); //取模运算来实现加减进位,这种构建结点的语法见结构体定义
//尾插法插入操作
r->next=temp;
r=temp;
carry = sum/10; //用carry保存每一轮的进位值,并作用到下一轮的计算中
//l1指针和l2指针移动操作
if(l1){
l1=l1->next;
}
if(l2){
l2=l2->next;
}
}
//!!!务必不要忽视对最后carry值的保存!!!
if(carry>0){
r->next=new ListNode(carry);
}
head = head->next; //将头指针指向第一个元素,不要头结点
return head;
}
};
最后果然的成绩虽然AC了,但这时间空间损耗也说明了我这算法的不友好性。