绪论:
值大三开学之际,选修算法分析与设计一课,开启博客记录下自己的学习足迹,以期于过程中加深心得
问题导入:
本系列所选题目皆来自于LeetCode网站,首先尝试的是以小端模式单向链表存储的大数加法,问题描述如下
数据类型以及接口已经定义好
/**
* 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) {}
};
解法分析:
链表非空,数字非负,小端模式存储也很符合加法从低位开始的竖式计算。直接new出一个ListNode实例,记录下地址用于返回,从最低位起逐个相加并存储结果
ListNode* rst = new ListNode(0);
ListNode* rtn = rst;
while (l1->next != NULL && l2->next != NULL) {
rst->val = rst->val + l1->val + l2->val;
int digit = rst->val/10;
rst->val %= 10;
rst->next = new ListNode(digit);
rst = rst->next;
l1 = l1->next;
l2 = l2->next;
}
这里特意留出最高位用于之后的进位处理。考虑到两个加数的位数可能不相等,最高位有无进位对之后的操作有一定影响,故而进行了分类讨论
rst->val = rst->val + l1->val + l2->val;
if (rst->val > 9) {
rst->val %= 10;
rst->next = new ListNode(1);
rst = rst->next;
l1 = l1->next;
l2 = l2->next;
if (l1 != NULL || l2 != NULL) {
ListNode* over = (l1 == NULL)? l2 : l1;
while (over->next != NULL) {
rst->val += over->val;
int dgt = rst->val/10;
rst->val %= 10;
rst->next = new ListNode(dgt);
rst = rst->next;
over = over->next;
}
rst->val += over->val;
if (rst->val > 9) {
rst->val %= 10;
rst->next = new ListNode(1);
}
}
} else {
l1 = l1->next;
l2 = l2->next;
if (l1 != NULL || l2 != NULL) {
ListNode* over = (l1 == NULL)? l2 : l1;
while (over != NULL) {
rst->next = new ListNode(0);
rst = rst->next;
rst->val += over->val;
over = over->next;
}
}
}
最后将首地址rtn返回即可
探究升华:
题目看似不难,但若在进位操作中未考虑到所有可能的情形,极容易出现谬误。同时也是局限于我的思考框架,代码看起来有些冗长。为此我fork一份比较优秀的solution以供学习参考
ListNode *addTwoNumbers(ListNode *l1, ListNode *l2) {
ListNode preHead(0), *p = &preHead;
int extra = 0;
while (l1 || l2 || extra) {
int sum = (l1 ? l1->val : 0) + (l2 ? l2->val : 0) + extra;
extra = sum / 10;
p->next = new ListNode(sum % 10);
p = p->next;
l1 = l1 ? l1->next : l1;
l2 = l2 ? l2->next : l2;
}
return preHead.next;
}
该解法设置了数值为0的头结点,运用到了计算机二进制加法器中的sum位和carry位,?表达式的灵活运用使得代码整体十分简洁有力。