最近在学校提供网站LeetCode上刷算法题,近段时间会将一些自己觉得有意义,值得效仿的点写下来,为自己以后的工作和学习所用。
今天讲的是Add Two Numbers
首先看一下问题描述
问题大概意思是这样的:分别将两个非负整数的各个数位用链表连接起来,且数字的存储方向是反向的,即,2->4->3其实是342。要求我们计算出两个整数的和并以相同方式返回。
同时,题目提供了这种链式的数据结构
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
刚读问题的时候我就在想:为什么要把数反向存储?后来拿了几个数试验了一下发现了这个的好处。以2->0->7(702)、8->0->8(808)为例,自然702+808=1510,按照我们正常的思维来顺序存储的话,那么运算必须从右往左,这是因为我们在没有计算低位的时候永远不知道是否会向高位进位,这就意味着我们必须先把指针移到末尾,然后再去逐个找父节点,这是ListNode这个数据结构无法实现的,同时也做了很多无用功(从开头将最高位指针移到末尾最低位指针)。
而反向存储完美地规避了这些问题,它本身初始节点就是最低位,这是我觉得这个问题设计得比较巧妙的点睛之笔,只是调换了个顺序,问题难度瞬间降下来了。
下面说一下我对这个问题的解决思想。很简单,遍历两个数列,一个变量sum记录对应位相加结果,一个变量carry记录进位,将sum取个位数作为这两个位相加的结果。遍历数列l1, l2最终会得到三个结果:
- l1,l2长度相同,同时遍历完
- l1先遍历完,l2较长
- l2先遍历完,l1较长
一开始我思想比较死板,严格将每种情况都用if语句乖乖地写下来了,但是现在我却想说:“管他呢!”。哪个先遍历完,就把它的指针设为NULL,在两个数列都没遍历完(不都是NULL)之前,sum只加上非NULL指针的val值就行了。最后等把两个数列都遍历完后,再看看是否产生了更高的进位即可。
现在思路清楚了,首先做准备工作
其实没必要设置ptr1,ptr2的,直接对参数取next也是可以的,因为这里是参数的副本,在这里的操作对参数本身是没有任何影响的,但是我觉得还是养成不碰参数的习惯较好。ptrForAnswer是为了扩展answer而准备的,最后函数结束返回answer即可。
接下来可以确定循环了
如何规避数列长度不一的情况呢,这是我自认为做得比较好的办法:
int sum = carry;
if (ptr1) {
sum += ptr1->val;
ptr1 = ptr1->next;
}
if (ptr2) {
sum += ptr2->val;
ptr2 = ptr2->next;
}
if语句做了两件事情,一、将数列指针移向下一位(有下一位的前提下);二、把该位的值并入了sum中(该数列存在这一位的前提下)。
将两个数列都遍历完后,该考虑是否最后还产生了一个进位,这很简单
如此这一个问题就解决了。速度也还不错。这个算法复杂度主要看参数l1,l2的长度,因此是O(n)。
个人而言,这个问题有两个亮点,第一就是它的反向存储,第二就是我对数列长度不一的问题的优化。详细可以看下面的源代码。
/* 问题描述
两个链式非负整数,存储是反方向的。
如4->3->2其实是234,要求计算出两个链式数据的和,
并按相同存储方式返回
*/
/* From https://leetcode.com/problems/add-two-numbers/description/ */
/* 2017-09-07 by 王世祺 */
/* build the struct ListNode */
#include<iostream>
using namespace std;
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
int carry = 0;
ListNode* ptr1 = l1->next;
ListNode* ptr2 = l2->next;
ListNode* answer = new ListNode((l1->val + l2->val) % 10);
ListNode* ptrForAnswer = answer;
carry = (l1->val + l2->val) >= 10 ? 1 : 0;
/* 在两个数长度不一样的时候,和计算只在相同长度范围内有效。 */
while (ptr1 || ptr2) {
int sum = carry;
if (ptr1) {
sum += ptr1->val;
ptr1 = ptr1->next;
}
if (ptr2) {
sum += ptr2->val;
ptr2 = ptr2->next;
}
ptrForAnswer->next = new ListNode(sum % 10);
carry = (sum) >= 10 ? 1 : 0;
ptrForAnswer = ptrForAnswer->next;
}
/* 结束,看是否有进位 */
if (carry == 1)
ptrForAnswer->next = new ListNode(1);
return answer;
}
int main() {
ListNode* l1 = new ListNode(3);
l1->next = new ListNode(7);
ListNode* l2 = new ListNode(9);
l2->next = new ListNode(2);
addTwoNumbers(l1, l2);
}