问题描述:
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4] 输出:[7,0,8] 解释:342 + 465 = 807.
思路分析
先写自己第一感:很自然地没想到从表头到表尾按每一位进位计算,想到可以分别把每个链表代表的值用整数来表示出来,即num=第一个节点*1+第二个节点*10+第三个节点*100+......则sum=num1+num2,再用%和/对sum拆解,从而正好逆序地保存到新的链表中并返回。
自己代码:
/**
* 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) {
//自己默认没有头结点
int s1,s2,s3;
int d1 = 1, d2 = 1; //每一位需要扩大的倍数
ListNode *p1 = l1, *p2 = l2, *p = l1; //p是防止l1跑丢了找不到
while(p1){
s1 += p1->val * d1;
d1 *= 10;
p1 = p1->next;
}
while(p2){
s2 += p2->val * d2;
d2 *= 10;
p2 = p2->next;
}
s3 = s1 + s2;
while(s3 != 0){
int a = s3%10;
s3 = s3/10;
//在l1原来的结构上修改
if(l1){
l1->val = a;
l1 = l1->next;
}else{
l1 = (ListNode*)malloc(sizeof(ListNode));
l1->val = a;
l1 = l1->next;
}
}
return p;
}
};
结果报错:
执行出错信息
Line 33: Char 21: runtime error: member access within misaligned address 0xbebebebebebebebe for type 'ListNode', which requires 8 byte alignment (solution.cpp) 0xbebebebebebebebe: note: pointer points here <memory cannot be printed> SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior prog_joined.cpp:42:21
最后执行的输入
[2,4,3] [5,6,4]
逻辑和程序大体是对的,可能是默认没有头结点而实际有头结点出了问题?可能是对形参l1进行了LeetCode不允许的迷之操作?去百度了一下
原因:
我们在访问某个变量时,因为这个变量中含有未赋值的指针。定义但是不赋值的指针叫做野指针。野指针指向不明,对程序有不可知的后果,引用了更是出大问题,所以,c语言严格反对野指针。我的程序报错指向“l1->val=a”这一行,就是说还是形参l1相关出了问题,我又看了题解有反映如下
分析到这,但具体我的代码如何改正,暂时还没解决。接下来看官方题解。
解决:后改进如下
/**
* 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) {
unsigned long long s1=0,s2=0,s3=0; //测试案例会很大,数据类型要注意
unsigned long long d1 = 1, d2 = 1;
ListNode *p1 = l1, *p2 = l2;
ListNode *l3 = new ListNode(), *p3 = l3; //这次尝试了用新的节点空间
while(p1){
s1 += p1->val * d1;
d1 *= 10;
p1 = p1->next;
}
while(p2){
s2 += p2->val * d2;
d2 *= 10;
p2 = p2->next;
}
s3 = s1 + s2;
//if语句是后来才加上,此种做法忽略了s3==0这种情况
if(s3==0){
return new ListNode(0);
}
while(s3 != 0){
int a = s3%10;
s3 = s3/10;
ListNode *s = new ListNode(a);
p3->next = s;
p3 = p3->next;
}
return l3->next;
}
};
首先“s3=s1+s2”就很蠢,万一爆掉怎么办?按位进行模拟高精度才是首选!过了大多数案例......
最后的输出结果很迷......
官方题解:
由于输入的两个链表都是逆序存储数字的位数的,因此两个链表中同一位置的数字可以直接相加。两个链表长度不一致?在短的那个后面补0!
同时遍历两个链表,逐位计算它们的和,并与当前位置的进位值相加。具体而言,如果当前两个链表处相应位置的数字为 n1,n2,进位值为carry,则答案链表处相应位置的数字为 (n1+n2+carry) mod 10,而新的进位值为⌊(n1+n2+carry)/10⌋。
这道题的思路不难,难的是最后的进位很容易忘,如果链表遍历结束后,有 carry>0,还需要在答案链表的后面附加一个节点,节点的值为 carry。
参考代码:
根据官方题解手动独立尝试并通过
/**
* 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 *p1 = l1, *p2 = l2;
ListNode *l3 = new ListNode();
l3->next = nullptr;
ListNode *p3 = l3;
int add = 0; //进位初始为0
while(p1||p2){
int n1 = p1?p1->val:0;
int n2 = p2?p2->val:0;
ListNode *s = new ListNode((add+n1+n2)%10);
p3->next = s;
s->next = nullptr;
add = (add+n1+n2)/10;
p1 = p1?p1->next:nullptr;p2 = p2?p2->next:nullptr;p3 = p3->next; //修改之前也疯狂地报错,原因是未考虑周全p1,p2是否为空,导致使用了空指针。具体见下方的文章引用
}
if(add!=0){
p3->next = new ListNode(add);
}
return l3->next;
}
};
例如:
在while循环中的 while(tempNode->next != nullptr && tempNode != nullptr)
这两条蓝色的判断语句中,对于&&符号而言,只要左表达式为false,那么右表达式则不会执行即可直接把while(中的整体表达式赋值为false了)。
类似的,对于||符号而言,(A || B)只要A表达式为true,那么右边的B表达式无论如何,都不会继续执行,直接让(中的整体表达式赋值为true了)
以后再遇到&&和||符号的判断,应注意判断顺序!
因此,这里因为若tempNode->next 已经==nullptr了, 那么就相当于你提前引用了ListNode的一个空指针,这是error的!
以后的coding中若再遇到这种case一定要想到这一个点!!!
总结:
从大一上学期就解除了算法数据结构这块,也看了《大话数据结构》《啊哈算法》,当时基本把每一种都搞清楚的大差不差,先后在洛谷、LeetCode等平台上凭兴趣做了一点题目,也做了刷题笔记。但是几个寒暑假一过,也没有及时的复习和复盘,结果真相了。总结就是,虽然提前学了,但仅仅是提前接受了别人灌输给自己的东西,并没有转化成自己的东西,这样是经不起时间和复杂情景变化的考验的。总结就是,重复重复再重复,每重复一遍,都会有新的认识,这个在我大一上之后反复战斗与这些基础算法数据结构而一直未推进更难更复杂的算法数据结构的历程中有所体现。踩了那么多坑,步步紧逼的秋招,在鞭策我精进算法数据结构方面,起了重大的决定性作用。
之前从未写过题解等技术博客,这篇是我的处女作,这么说,我不认为刷题写题解以这种博文的形式是最佳的,也许我的时间并不充裕,也许我可以省下博文中与读者互动、编排样式等时间去多做几道题、多深层次考虑本道题......但我觉得写博文也有很大的乐趣和意义,总比打几把王者停留在钻石啥的快乐多了。
总之,至少也要保证每日一题(考虑到期末周),路飞大人说的对,不要堆积技术栈的使用,要克服自己的舒适区,挑战自身的潜力。